LBFactory.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. /**
  3. * @file
  4. * @ingroup Database
  5. */
  6. /**
  7. * An interface for generating database load balancers
  8. * @ingroup Database
  9. */
  10. abstract class LBFactory {
  11. static $instance;
  12. /**
  13. * Get an LBFactory instance
  14. */
  15. static function &singleton() {
  16. if ( is_null( self::$instance ) ) {
  17. global $wgLBFactoryConf;
  18. $class = $wgLBFactoryConf['class'];
  19. self::$instance = new $class( $wgLBFactoryConf );
  20. }
  21. return self::$instance;
  22. }
  23. /**
  24. * Shut down, close connections and destroy the cached instance.
  25. *
  26. */
  27. static function destroyInstance() {
  28. if ( self::$instance ) {
  29. self::$instance->shutdown();
  30. self::$instance->forEachLBCallMethod( 'closeAll' );
  31. self::$instance = null;
  32. }
  33. }
  34. /**
  35. * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
  36. */
  37. abstract function __construct( $conf );
  38. /**
  39. * Create a new load balancer object. The resulting object will be untracked,
  40. * not chronology-protected, and the caller is responsible for cleaning it up.
  41. *
  42. * @param string $wiki Wiki ID, or false for the current wiki
  43. * @return LoadBalancer
  44. */
  45. abstract function newMainLB( $wiki = false );
  46. /**
  47. * Get a cached (tracked) load balancer object.
  48. *
  49. * @param string $wiki Wiki ID, or false for the current wiki
  50. * @return LoadBalancer
  51. */
  52. abstract function getMainLB( $wiki = false );
  53. /*
  54. * Create a new load balancer for external storage. The resulting object will be
  55. * untracked, not chronology-protected, and the caller is responsible for
  56. * cleaning it up.
  57. *
  58. * @param string $cluster External storage cluster, or false for core
  59. * @param string $wiki Wiki ID, or false for the current wiki
  60. */
  61. abstract function newExternalLB( $cluster, $wiki = false );
  62. /*
  63. * Get a cached (tracked) load balancer for external storage
  64. *
  65. * @param string $cluster External storage cluster, or false for core
  66. * @param string $wiki Wiki ID, or false for the current wiki
  67. */
  68. abstract function &getExternalLB( $cluster, $wiki = false );
  69. /**
  70. * Execute a function for each tracked load balancer
  71. * The callback is called with the load balancer as the first parameter,
  72. * and $params passed as the subsequent parameters.
  73. */
  74. abstract function forEachLB( $callback, $params = array() );
  75. /**
  76. * Prepare all tracked load balancers for shutdown
  77. * STUB
  78. */
  79. function shutdown() {}
  80. /**
  81. * Call a method of each tracked load balancer
  82. */
  83. function forEachLBCallMethod( $methodName, $args = array() ) {
  84. $this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
  85. }
  86. /**
  87. * Private helper for forEachLBCallMethod
  88. */
  89. function callMethod( $loadBalancer, $methodName, $args ) {
  90. call_user_func_array( array( $loadBalancer, $methodName ), $args );
  91. }
  92. /**
  93. * Commit changes on all master connections
  94. */
  95. function commitMasterChanges() {
  96. $this->forEachLBCallMethod( 'commitMasterChanges' );
  97. }
  98. }
  99. /**
  100. * A simple single-master LBFactory that gets its configuration from the b/c globals
  101. */
  102. class LBFactory_Simple extends LBFactory {
  103. var $mainLB;
  104. var $extLBs = array();
  105. # Chronology protector
  106. var $chronProt;
  107. function __construct( $conf ) {
  108. $this->chronProt = new ChronologyProtector;
  109. }
  110. function newMainLB( $wiki = false ) {
  111. global $wgDBservers, $wgMasterWaitTimeout;
  112. if ( $wgDBservers ) {
  113. $servers = $wgDBservers;
  114. } else {
  115. global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
  116. $servers = array(array(
  117. 'host' => $wgDBserver,
  118. 'user' => $wgDBuser,
  119. 'password' => $wgDBpassword,
  120. 'dbname' => $wgDBname,
  121. 'type' => $wgDBtype,
  122. 'load' => 1,
  123. 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
  124. ));
  125. }
  126. return new LoadBalancer( array(
  127. 'servers' => $servers,
  128. 'masterWaitTimeout' => $wgMasterWaitTimeout
  129. ));
  130. }
  131. function getMainLB( $wiki = false ) {
  132. if ( !isset( $this->mainLB ) ) {
  133. $this->mainLB = $this->newMainLB( $wiki );
  134. $this->mainLB->parentInfo( array( 'id' => 'main' ) );
  135. $this->chronProt->initLB( $this->mainLB );
  136. }
  137. return $this->mainLB;
  138. }
  139. function newExternalLB( $cluster, $wiki = false ) {
  140. global $wgExternalServers;
  141. if ( !isset( $wgExternalServers[$cluster] ) ) {
  142. throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
  143. }
  144. return new LoadBalancer( array(
  145. 'servers' => $wgExternalServers[$cluster]
  146. ));
  147. }
  148. function &getExternalLB( $cluster, $wiki = false ) {
  149. if ( !isset( $this->extLBs[$cluster] ) ) {
  150. $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
  151. $this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
  152. }
  153. return $this->extLBs[$cluster];
  154. }
  155. /**
  156. * Execute a function for each tracked load balancer
  157. * The callback is called with the load balancer as the first parameter,
  158. * and $params passed as the subsequent parameters.
  159. */
  160. function forEachLB( $callback, $params = array() ) {
  161. if ( isset( $this->mainLB ) ) {
  162. call_user_func_array( $callback, array_merge( array( $this->mainLB ), $params ) );
  163. }
  164. foreach ( $this->extLBs as $lb ) {
  165. call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
  166. }
  167. }
  168. function shutdown() {
  169. if ( $this->mainLB ) {
  170. $this->chronProt->shutdownLB( $this->mainLB );
  171. }
  172. $this->chronProt->shutdown();
  173. $this->commitMasterChanges();
  174. }
  175. }
  176. /**
  177. * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
  178. * Kind of like Hawking's [[Chronology Protection Agency]].
  179. */
  180. class ChronologyProtector {
  181. var $startupPos;
  182. var $shutdownPos = array();
  183. /**
  184. * Initialise a LoadBalancer to give it appropriate chronology protection.
  185. *
  186. * @param LoadBalancer $lb
  187. */
  188. function initLB( $lb ) {
  189. if ( $this->startupPos === null ) {
  190. if ( !empty( $_SESSION[__CLASS__] ) ) {
  191. $this->startupPos = $_SESSION[__CLASS__];
  192. }
  193. }
  194. if ( !$this->startupPos ) {
  195. return;
  196. }
  197. $masterName = $lb->getServerName( 0 );
  198. if ( $lb->getServerCount() > 1 && !empty( $this->startupPos[$masterName] ) ) {
  199. $info = $lb->parentInfo();
  200. $pos = $this->startupPos[$masterName];
  201. wfDebug( __METHOD__.": LB " . $info['id'] . " waiting for master pos $pos\n" );
  202. $lb->waitFor( $this->startupPos[$masterName] );
  203. }
  204. }
  205. /**
  206. * Notify the ChronologyProtector that the LoadBalancer is about to shut
  207. * down. Saves replication positions.
  208. *
  209. * @param LoadBalancer $lb
  210. */
  211. function shutdownLB( $lb ) {
  212. // Don't start a session, don't bother with non-replicated setups
  213. if ( strval( session_id() ) == '' || $lb->getServerCount() <= 1 ) {
  214. return;
  215. }
  216. $masterName = $lb->getServerName( 0 );
  217. if ( isset( $this->shutdownPos[$masterName] ) ) {
  218. // Already done
  219. return;
  220. }
  221. // Only save the position if writes have been done on the connection
  222. $db = $lb->getAnyOpenConnection( 0 );
  223. $info = $lb->parentInfo();
  224. if ( !$db || !$db->doneWrites() ) {
  225. wfDebug( __METHOD__.": LB {$info['id']}, no writes done\n" );
  226. return;
  227. }
  228. $pos = $db->getMasterPos();
  229. wfDebug( __METHOD__.": LB {$info['id']} has master pos $pos\n" );
  230. $this->shutdownPos[$masterName] = $pos;
  231. }
  232. /**
  233. * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
  234. * May commit chronology data to persistent storage.
  235. */
  236. function shutdown() {
  237. if ( session_id() != '' && count( $this->shutdownPos ) ) {
  238. wfDebug( __METHOD__.": saving master pos for " .
  239. count( $this->shutdownPos ) . " master(s)\n" );
  240. $_SESSION[__CLASS__] = $this->shutdownPos;
  241. }
  242. }
  243. }