<?php
/**
+ * Generator of database load balancing objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Database
*/
* @ingroup Database
*/
abstract class LBFactory {
+
+ /**
+ * @var LBFactory
+ */
static $instance;
+ /**
+ * Disables all access to the load balancer, will cause all database access
+ * to throw a DBAccessError
+ */
+ public static function disableBackend() {
+ global $wgLBFactoryConf;
+ self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+ }
+
/**
* Get an LBFactory instance
+ *
+ * @return LBFactory
*/
static function &singleton() {
if ( is_null( self::$instance ) ) {
}
/**
- * Destory the instance
- * Actually used by maintenace/parserTests.inc to force to reopen connection
- * when $wgDBprefix has changed
+ * Shut down, close connections and destroy the cached instance.
*/
- static function destroy(){
- self::$instance = null;
+ static function destroyInstance() {
+ if ( self::$instance ) {
+ self::$instance->shutdown();
+ self::$instance->forEachLBCallMethod( 'closeAll' );
+ self::$instance = null;
+ }
+ }
+
+ /**
+ * Set the instance to be the given object
+ *
+ * @param $instance LBFactory
+ */
+ static function setInstance( $instance ) {
+ self::destroyInstance();
+ self::$instance = $instance;
}
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
+ * @param $conf
*/
abstract function __construct( $conf );
/**
- * Get a load balancer object.
+ * Create a new load balancer object. The resulting object will be untracked,
+ * not chronology-protected, and the caller is responsible for cleaning it up.
+ *
+ * @param $wiki String: wiki ID, or false for the current wiki
+ * @return LoadBalancer
+ */
+ abstract function newMainLB( $wiki = false );
+
+ /**
+ * Get a cached (tracked) load balancer object.
*
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $wiki String: wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
- /*
- * Get a load balancer for external storage
+ /**
+ * Create a new load balancer for external storage. The resulting object will be
+ * untracked, not chronology-protected, and the caller is responsible for
+ * cleaning it up.
+ *
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
*
- * @param string $cluster External storage cluster, or false for core
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @return LoadBalancer
+ */
+ abstract function newExternalLB( $cluster, $wiki = false );
+
+ /**
+ * Get a cached (tracked) load balancer for external storage
+ *
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
+ *
+ * @return LoadBalancer
*/
abstract function &getExternalLB( $cluster, $wiki = false );
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
+ * @param $callback string|array
+ * @param array $params
*/
abstract function forEachLB( $callback, $params = array() );
/**
- * Prepare all load balancers for shutdown
+ * Prepare all tracked load balancers for shutdown
* STUB
*/
function shutdown() {}
/**
- * Call a method of each load balancer
+ * Call a method of each tracked load balancer
+ * @param $methodName string
+ * @param $args array
*/
function forEachLBCallMethod( $methodName, $args = array() ) {
$this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
/**
* Private helper for forEachLBCallMethod
+ * @param $loadBalancer
+ * @param $methodName string
+ * @param $args
*/
function callMethod( $loadBalancer, $methodName, $args ) {
call_user_func_array( array( $loadBalancer, $methodName ), $args );
* A simple single-master LBFactory that gets its configuration from the b/c globals
*/
class LBFactory_Simple extends LBFactory {
+
+ /**
+ * @var LoadBalancer
+ */
var $mainLB;
var $extLBs = array();
$this->chronProt = new ChronologyProtector;
}
- function getMainLB( $wiki = false ) {
- if ( !isset( $this->mainLB ) ) {
- global $wgDBservers, $wgMasterWaitTimeout;
- if ( !$wgDBservers ) {
- global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
- $wgDBservers = array(array(
- 'host' => $wgDBserver,
- 'user' => $wgDBuser,
- 'password' => $wgDBpassword,
- 'dbname' => $wgDBname,
- 'type' => $wgDBtype,
- 'load' => 1,
- 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
- ));
+ /**
+ * @param $wiki
+ * @return LoadBalancer
+ */
+ function newMainLB( $wiki = false ) {
+ global $wgDBservers, $wgMasterWaitTimeout;
+ if ( $wgDBservers ) {
+ $servers = $wgDBservers;
+ } else {
+ global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
+ global $wgDBssl, $wgDBcompress;
+
+ $flags = ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT;
+ if ( $wgDBssl ) {
+ $flags |= DBO_SSL;
+ }
+ if ( $wgDBcompress ) {
+ $flags |= DBO_COMPRESS;
}
- $this->mainLB = new LoadBalancer( array(
- 'servers' => $wgDBservers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
+ $servers = array(array(
+ 'host' => $wgDBserver,
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'dbname' => $wgDBname,
+ 'type' => $wgDBtype,
+ 'load' => 1,
+ 'flags' => $flags
));
+ }
+
+ return new LoadBalancer( array(
+ 'servers' => $servers,
+ 'masterWaitTimeout' => $wgMasterWaitTimeout
+ ));
+ }
+
+ /**
+ * @param $wiki
+ * @return LoadBalancer
+ */
+ function getMainLB( $wiki = false ) {
+ if ( !isset( $this->mainLB ) ) {
+ $this->mainLB = $this->newMainLB( $wiki );
$this->mainLB->parentInfo( array( 'id' => 'main' ) );
$this->chronProt->initLB( $this->mainLB );
}
return $this->mainLB;
}
- function &getExternalLB( $cluster, $wiki = false ) {
+ /**
+ * @throws MWException
+ * @param $cluster
+ * @param $wiki
+ * @return LoadBalancer
+ */
+ function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
+ if ( !isset( $wgExternalServers[$cluster] ) ) {
+ throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+ }
+ return new LoadBalancer( array(
+ 'servers' => $wgExternalServers[$cluster]
+ ));
+ }
+
+ /**
+ * @param $cluster
+ * @param $wiki
+ * @return array
+ */
+ function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
- if ( !isset( $wgExternalServers[$cluster] ) ) {
- throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
- }
- $this->extLBs[$cluster] = new LoadBalancer( array(
- 'servers' => $wgExternalServers[$cluster]
- ));
+ $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
}
return $this->extLBs[$cluster];
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
+ * @param $callback
+ * @param $params array
*/
function forEachLB( $callback, $params = array() ) {
if ( isset( $this->mainLB ) ) {
}
}
+/**
+ * LBFactory class that throws an error on any attempt to use it.
+ * This will typically be done via wfGetDB().
+ * Call LBFactory::disableBackend() to start using this, and
+ * LBFactory::enableBackend() to return to normal behavior
+ */
+class LBFactory_Fake extends LBFactory {
+ function __construct( $conf ) {}
+
+ function newMainLB( $wiki = false) {
+ throw new DBAccessError;
+ }
+ function getMainLB( $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function newExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function &getExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function forEachLB( $callback, $params = array() ) {}
+}
+
+/**
+ * Exception class for attempted DB access
+ */
+class DBAccessError extends MWException {
+ function __construct() {
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+ }
+}
+
/**
* Class for ensuring a consistent ordering of events as seen by the user, despite replication.
* Kind of like Hawking's [[Chronology Protection Agency]].
/**
* Initialise a LoadBalancer to give it appropriate chronology protection.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function initLB( $lb ) {
if ( $this->startupPos === null ) {
if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) {
$info = $lb->parentInfo();
$pos = $this->startupPos[$masterName];
- wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" );
+ wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" );
$lb->waitFor( $this->startupPos[$masterName] );
}
}
* Notify the ChronologyProtector that the LoadBalancer is about to shut
* down. Saves replication positions.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function shutdownLB( $lb ) {
- if ( session_id() != '' && $lb->getServerCount() > 1 ) {
- $masterName = $lb->getServerName( 0 );
- if ( !isset( $this->shutdownPos[$masterName] ) ) {
- $pos = $lb->getMasterPos();
- $info = $lb->parentInfo();
- wfDebug( __METHOD__.": LB " . $info['id'] . " has master pos $pos\n" );
- $this->shutdownPos[$masterName] = $pos;
- }
+ // Don't start a session, don't bother with non-replicated setups
+ if ( strval( session_id() ) == '' || $lb->getServerCount() <= 1 ) {
+ return;
+ }
+ $masterName = $lb->getServerName( 0 );
+ if ( isset( $this->shutdownPos[$masterName] ) ) {
+ // Already done
+ return;
+ }
+ // Only save the position if writes have been done on the connection
+ $db = $lb->getAnyOpenConnection( 0 );
+ $info = $lb->parentInfo();
+ if ( !$db || !$db->doneWrites() ) {
+ wfDebug( __METHOD__ . ": LB {$info['id']}, no writes done\n" );
+ return;
}
+ $pos = $db->getMasterPos();
+ wfDebug( __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
+ $this->shutdownPos[$masterName] = $pos;
}
/**
*/
function shutdown() {
if ( session_id() != '' && count( $this->shutdownPos ) ) {
- wfDebug( __METHOD__.": saving master pos for " .
+ wfDebug( __METHOD__ . ": saving master pos for " .
count( $this->shutdownPos ) . " master(s)\n" );
$_SESSION[__CLASS__] = $this->shutdownPos;
}