MediaWiki  master
MWLBFactory.php
Go to the documentation of this file.
1 <?php
24 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
34 use Wikimedia\RequestTimeout\CriticalSectionProvider;
35 
42 class MWLBFactory {
43 
45  private static $loggedDeprecations = [];
46 
51  'CommandLineMode',
52  MainConfigNames::DBcompress,
53  MainConfigNames::DBDefaultGroup,
54  MainConfigNames::DBmwschema,
55  MainConfigNames::DBname,
56  MainConfigNames::DBpassword,
57  MainConfigNames::DBport,
58  MainConfigNames::DBprefix,
59  MainConfigNames::DBserver,
60  MainConfigNames::DBservers,
61  MainConfigNames::DBssl,
62  MainConfigNames::DBtype,
63  MainConfigNames::DBuser,
64  MainConfigNames::DebugDumpSql,
65  MainConfigNames::DebugLogFile,
66  MainConfigNames::DebugToolbar,
67  MainConfigNames::ExternalServers,
68  MainConfigNames::SQLiteDataDir,
69  MainConfigNames::SQLMode,
70  ];
74  private $options;
78  private $readOnlyMode;
82  private $chronologyProtector;
86  private $srvCache;
90  private $wanCache;
94  private $csProvider;
98  private $statsdDataFactory;
99 
109  public function __construct(
110  ServiceOptions $options,
111  ConfiguredReadOnlyMode $readOnlyMode,
112  ChronologyProtector $chronologyProtector,
113  BagOStuff $srvCache,
114  WANObjectCache $wanCache,
115  CriticalSectionProvider $csProvider,
116  StatsdDataFactoryInterface $statsdDataFactory
117  ) {
118  $this->options = $options;
119  $this->readOnlyMode = $readOnlyMode;
120  $this->chronologyProtector = $chronologyProtector;
121  $this->srvCache = $srvCache;
122  $this->wanCache = $wanCache;
123  $this->csProvider = $csProvider;
124  $this->statsdDataFactory = $statsdDataFactory;
125  }
126 
132  public function applyDefaultConfig( array $lbConf ) {
133  $this->options->assertRequiredOptions( self::APPLY_DEFAULT_CONFIG_OPTIONS );
134 
135  $typesWithSchema = self::getDbTypesWithSchemas();
136  if ( Profiler::instance() instanceof ProfilerStub ) {
137  $profilerCallback = null;
138  } else {
139  $profilerCallback = static function ( $section ) {
140  return Profiler::instance()->scopedProfileIn( $section );
141  };
142  }
143 
144  $lbConf += [
145  'localDomain' => new DatabaseDomain(
146  $this->options->get( MainConfigNames::DBname ),
147  $this->options->get( MainConfigNames::DBmwschema ),
148  $this->options->get( MainConfigNames::DBprefix )
149  ),
150  'profiler' => $profilerCallback,
151  'trxProfiler' => Profiler::instance()->getTransactionProfiler(),
152  'logger' => LoggerFactory::getInstance( 'rdbms' ),
153  'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
154  'deprecationLogger' => [ static::class, 'logDeprecation' ],
155  'statsdDataFactory' => $this->statsdDataFactory,
156  'cliMode' => $this->options->get( 'CommandLineMode' ),
157  'readOnlyReason' => $this->readOnlyMode->getReason(),
158  'defaultGroup' => $this->options->get( MainConfigNames::DBDefaultGroup ),
159  'criticalSectionProvider' => $this->csProvider
160  ];
161 
162  $serversCheck = [];
163  // When making changes here, remember to also specify MediaWiki-specific options
164  // for Database classes in the relevant Installer subclass.
165  // Such as MysqlInstaller::openConnection and PostgresInstaller::openConnectionWithParams.
166  if ( $lbConf['class'] === Wikimedia\Rdbms\LBFactorySimple::class ) {
167  if ( isset( $lbConf['servers'] ) ) {
168  // Server array is already explicitly configured
169  } elseif ( is_array( $this->options->get( MainConfigNames::DBservers ) ) ) {
170  $lbConf['servers'] = [];
171  foreach ( $this->options->get( MainConfigNames::DBservers ) as $i => $server ) {
172  $lbConf['servers'][$i] = self::initServerInfo( $server, $this->options );
173  }
174  } else {
175  $server = self::initServerInfo(
176  [
177  'host' => $this->options->get( MainConfigNames::DBserver ),
178  'user' => $this->options->get( MainConfigNames::DBuser ),
179  'password' => $this->options->get( MainConfigNames::DBpassword ),
180  'dbname' => $this->options->get( MainConfigNames::DBname ),
181  'type' => $this->options->get( MainConfigNames::DBtype ),
182  'load' => 1
183  ],
184  $this->options
185  );
186 
187  if ( $this->options->get( MainConfigNames::DBssl ) ) {
188  $server['ssl'] = true;
189  }
190  $server['flags'] |= $this->options->get( MainConfigNames::DBcompress ) ? DBO_COMPRESS : 0;
191 
192  $lbConf['servers'] = [ $server ];
193  }
194  if ( !isset( $lbConf['externalClusters'] ) ) {
195  $lbConf['externalClusters'] = $this->options->get( MainConfigNames::ExternalServers );
196  }
197 
198  $serversCheck = $lbConf['servers'];
199  } elseif ( $lbConf['class'] === Wikimedia\Rdbms\LBFactoryMulti::class ) {
200  if ( isset( $lbConf['serverTemplate'] ) ) {
201  if ( in_array( $lbConf['serverTemplate']['type'], $typesWithSchema, true ) ) {
202  $lbConf['serverTemplate']['schema'] = $this->options->get( MainConfigNames::DBmwschema );
203  }
204  $lbConf['serverTemplate']['sqlMode'] = $this->options->get( MainConfigNames::SQLMode );
205  $serversCheck = [ $lbConf['serverTemplate'] ];
206  }
207  }
208 
209  self::assertValidServerConfigs(
210  $serversCheck,
211  $this->options->get( MainConfigNames::DBname ),
212  $this->options->get( MainConfigNames::DBprefix )
213  );
214 
215  $lbConf['chronologyProtector'] = $this->chronologyProtector;
216  $lbConf['srvCache'] = $this->srvCache;
217  $lbConf['wanCache'] = $this->wanCache;
218 
219  return $lbConf;
220  }
221 
225  private function getDbTypesWithSchemas() {
226  return [ 'postgres' ];
227  }
228 
234  private function initServerInfo( array $server, ServiceOptions $options ) {
235  if ( $server['type'] === 'sqlite' ) {
236  $httpMethod = $_SERVER['REQUEST_METHOD'] ?? null;
237  // T93097: hint for how file-based databases (e.g. sqlite) should go about locking.
238  // See https://www.sqlite.org/lang_transaction.html
239  // See https://www.sqlite.org/lockingv3.html#shared_lock
240  $isHttpRead = in_array( $httpMethod, [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
241  if ( MW_ENTRY_POINT === 'rest' && !$isHttpRead ) {
242  // Hack to support some re-entrant invocations using sqlite
243  // See: T259685, T91820
245  if ( $request->hasHeader( 'Promise-Non-Write-API-Action' ) ) {
246  $isHttpRead = true;
247  }
248  }
249  $server += [
250  'dbDirectory' => $options->get( MainConfigNames::SQLiteDataDir ),
251  'trxMode' => $isHttpRead ? 'DEFERRED' : 'IMMEDIATE'
252  ];
253  } elseif ( $server['type'] === 'postgres' ) {
254  $server += [ 'port' => $options->get( MainConfigNames::DBport ) ];
255  }
256 
257  if ( in_array( $server['type'], self::getDbTypesWithSchemas(), true ) ) {
258  $server += [ 'schema' => $options->get( MainConfigNames::DBmwschema ) ];
259  }
260 
261  $flags = $server['flags'] ?? DBO_DEFAULT;
262  if ( $options->get( MainConfigNames::DebugDumpSql )
263  || $options->get( MainConfigNames::DebugLogFile )
264  || $options->get( MainConfigNames::DebugToolbar )
265  ) {
266  $flags |= DBO_DEBUG;
267  }
268  $server['flags'] = $flags;
269 
270  $server += [
271  'tablePrefix' => $options->get( MainConfigNames::DBprefix ),
272  'sqlMode' => $options->get( MainConfigNames::SQLMode ),
273  ];
274 
275  return $server;
276  }
277 
283  private function assertValidServerConfigs( array $servers, $ldDB, $ldTP ) {
284  foreach ( $servers as $server ) {
285  $type = $server['type'] ?? null;
286  $srvDB = $server['dbname'] ?? null; // server DB
287  $srvTP = $server['tablePrefix'] ?? ''; // server table prefix
288 
289  if ( $type === 'mysql' ) {
290  // A DB name is not needed to connect to mysql; 'dbname' is useless.
291  // This field only defines the DB to use for unspecified DB domains.
292  if ( $srvDB !== null && $srvDB !== $ldDB ) {
293  self::reportMismatchedDBs( $srvDB, $ldDB );
294  }
295  } elseif ( $type === 'postgres' ) {
296  if ( $srvTP !== '' ) {
297  self::reportIfPrefixSet( $srvTP, $type );
298  }
299  }
300 
301  if ( $srvTP !== '' && $srvTP !== $ldTP ) {
302  self::reportMismatchedPrefixes( $srvTP, $ldTP );
303  }
304  }
305  }
306 
312  private function reportIfPrefixSet( $prefix, $dbType ) {
313  $e = new UnexpectedValueException(
314  "\$wgDBprefix is set to '$prefix' but the database type is '$dbType'. " .
315  "MediaWiki does not support using a table prefix with this RDBMS type."
316  );
318  exit;
319  }
320 
326  private function reportMismatchedDBs( $srvDB, $ldDB ) {
327  $e = new UnexpectedValueException(
328  "\$wgDBservers has dbname='$srvDB' but \$wgDBname='$ldDB'. " .
329  "Set \$wgDBname to the database used by this wiki project. " .
330  "There is rarely a need to set 'dbname' in \$wgDBservers. " .
331  "Cross-wiki database access, use of WikiMap::getCurrentWikiDbDomain(), " .
332  "use of Database::getDomainId(), and other features are not reliable when " .
333  "\$wgDBservers does not match the local wiki database/prefix."
334  );
336  exit;
337  }
338 
344  private function reportMismatchedPrefixes( $srvTP, $ldTP ) {
345  $e = new UnexpectedValueException(
346  "\$wgDBservers has tablePrefix='$srvTP' but \$wgDBprefix='$ldTP'. " .
347  "Set \$wgDBprefix to the table prefix used by this wiki project. " .
348  "There is rarely a need to set 'tablePrefix' in \$wgDBservers. " .
349  "Cross-wiki database access, use of WikiMap::getCurrentWikiDbDomain(), " .
350  "use of Database::getDomainId(), and other features are not reliable when " .
351  "\$wgDBservers does not match the local wiki database/prefix."
352  );
354  exit;
355  }
356 
364  public function getLBFactoryClass( array $config ) {
365  $compat = [
366  // For LocalSettings.php compat after removing underscores (since 1.23).
367  'LBFactory_Single' => Wikimedia\Rdbms\LBFactorySingle::class,
368  'LBFactory_Simple' => Wikimedia\Rdbms\LBFactorySimple::class,
369  'LBFactory_Multi' => Wikimedia\Rdbms\LBFactoryMulti::class,
370  // For LocalSettings.php compat after moving classes to namespaces (since 1.29).
371  'LBFactorySingle' => Wikimedia\Rdbms\LBFactorySingle::class,
372  'LBFactorySimple' => Wikimedia\Rdbms\LBFactorySimple::class,
373  'LBFactoryMulti' => Wikimedia\Rdbms\LBFactoryMulti::class
374  ];
375 
376  $class = $config['class'];
377  return $compat[$class] ?? $class;
378  }
379 
383  public function setDomainAliases( ILBFactory $lbFactory ) {
384  $domain = DatabaseDomain::newFromId( $lbFactory->getLocalDomainID() );
385  // For compatibility with hyphenated $wgDBname values on older wikis, handle callers
386  // that assume corresponding database domain IDs and wiki IDs have identical values
387  $rawLocalDomain = strlen( $domain->getTablePrefix() )
388  ? "{$domain->getDatabase()}-{$domain->getTablePrefix()}"
389  : (string)$domain->getDatabase();
390 
391  $lbFactory->setDomainAliases( [ $rawLocalDomain => $domain ] );
392  }
393 
417  public function applyGlobalState(
418  ILBFactory $lbFactory,
419  Config $config,
421  ): void {
422  if ( $config->get( 'CommandLineMode' ) ) {
423  $lbFactory->getMainLB()->setTransactionListener(
424  __METHOD__,
425  static function ( $trigger ) use ( $stats, $config ) {
426  // During maintenance scripts and PHPUnit integration tests, we let
427  // DeferredUpdates run immediately from addUpdate(), unless a transaction
428  // is active. Notify DeferredUpdates after any commit to try now.
429  // See DeferredUpdates::tryOpportunisticExecute for why.
430  if ( $trigger === IDatabase::TRIGGER_COMMIT ) {
432  }
433  // Flush stats periodically in long-running CLI scripts to avoid OOM (T181385)
434  MediaWiki::emitBufferedStatsdData( $stats, $config );
435  }
436  );
437  $lbFactory->setWaitForReplicationListener(
438  __METHOD__,
439  static function () use ( $stats, $config ) {
440  // Flush stats periodically in long-running CLI scripts to avoid OOM (T181385)
441  MediaWiki::emitBufferedStatsdData( $stats, $config );
442  }
443  );
444 
445  }
446  }
447 
453  public static function logDeprecation( $msg ) {
454  if ( isset( self::$loggedDeprecations[$msg] ) ) {
455  return;
456  }
457  self::$loggedDeprecations[$msg] = true;
458  MWDebug::sendRawDeprecated( $msg, true, wfGetCaller() );
459  }
460 }
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
const MW_ENTRY_POINT
Definition: api.php:44
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:85
static tryOpportunisticExecute()
Consume and execute pending updates now if possible, instead of waiting.
static sendRawDeprecated( $msg, $sendToLog=true, $callerFunc='')
Send a raw deprecation message to the log and the debug toolbar, without filtering of duplicate messa...
Definition: MWDebug.php:374
static output(Throwable $e, $mode, Throwable $eNew=null)
MediaWiki-specific class for generating database load balancers.
Definition: MWLBFactory.php:42
static logDeprecation( $msg)
Log a database deprecation warning.
const APPLY_DEFAULT_CONFIG_OPTIONS
Definition: MWLBFactory.php:50
setDomainAliases(ILBFactory $lbFactory)
applyDefaultConfig(array $lbConf)
applyGlobalState(ILBFactory $lbFactory, Config $config, IBufferingStatsdDataFactory $stats)
Apply global state from the current web request or other PHP process.
__construct(ServiceOptions $options, ConfiguredReadOnlyMode $readOnlyMode, ChronologyProtector $chronologyProtector, BagOStuff $srvCache, WANObjectCache $wanCache, CriticalSectionProvider $csProvider, StatsdDataFactoryInterface $statsdDataFactory)
getLBFactoryClass(array $config)
Decide which LBFactory class to use.
A class for passing options to services.
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
Definition: MediaWiki.php:1212
Stub profiler that does nothing.
static instance()
Singleton.
Definition: Profiler.php:108
Multi-datacenter aware caching interface.
Provide a given client with protection against visible database lag.
Determine whether a site is statically configured as read-only.
Class to handle database/schema/prefix specifications for IDatabase.
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Interface for configuration instances.
Definition: Config.php:32
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:36
Manager of ILoadBalancer objects and, indirectly, IDatabase connections.
Definition: ILBFactory.php:46
getLocalDomainID()
Get the local (and default) database domain ID of connection handles.
setDomainAliases(array $aliases)
Convert certain database domains to alternative ones.
getMainLB( $domain=false)
Get the tracked load balancer instance for the main cluster that handles the given domain.
setWaitForReplicationListener( $name, callable $callback=null)
Add a callback to be run in every call to waitForReplication() before waiting.
const DBO_COMPRESS
Definition: defines.php:19
const DBO_DEFAULT
Definition: defines.php:13
const DBO_DEBUG
Definition: defines.php:9