aboutsummaryrefslogtreecommitdiffstats
path: root/includes
diff options
context:
space:
mode:
authordaniel <dkinzler@wikimedia.org>2023-01-25 16:11:10 +0100
committerDaniel Kinzler <dkinzler@wikimedia.org>2023-02-16 21:11:33 +0000
commit7dcfbf2a6263278a7385f380778584d084a79a92 (patch)
treeda9aae8198110ac0fccf9d253c6a19d8449730b2 /includes
parentfd660e4c519db8a568050455662d25a132cfb278 (diff)
downloadmediawikicore-7dcfbf2a6263278a7385f380778584d084a79a92.tar.gz
mediawikicore-7dcfbf2a6263278a7385f380778584d084a79a92.zip
Allow some maintenance scripts to be called without a LocalSettings
Subclasses of Maintenance that can function without database access can now override canExecuteWithoutLocalSettings to return true. This is useful for scripts that need to be able to run before or without installing MediaWiki. When no config file is present, the storage backend will be disabled. Any attempt to access the database will result in an error. NOTE: This makes MediaWikiServices::disableBackendServices() more comprehensive, to avoid premature failures during service construction. This change makes it necessary to adjust how the installer manages service overrides. The CLI installer is covered by CI, I tested the web installer manually. Change-Id: Ie84010c80f32cbdfd34aff9dde1bfde1ed531793
Diffstat (limited to 'includes')
-rw-r--r--includes/MediaWikiServices.php97
-rw-r--r--includes/installer/Installer.php53
-rw-r--r--includes/libs/rdbms/lbfactory/LBFactorySingle.php35
-rw-r--r--includes/libs/rdbms/loadbalancer/LoadBalancerDisabled.php115
-rw-r--r--includes/objectcache/ObjectCache.php11
5 files changed, 270 insertions, 41 deletions
diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php
index 0acefda6ecac..429b7b02213a 100644
--- a/includes/MediaWikiServices.php
+++ b/includes/MediaWikiServices.php
@@ -76,6 +76,7 @@ use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Interwiki\InterwikiLookup;
+use MediaWiki\Interwiki\NullInterwikiLookup;
use MediaWiki\JobQueue\JobFactory;
use MediaWiki\JobQueue\JobQueueGroupFactory;
use MediaWiki\Json\JsonCodec;
@@ -198,6 +199,8 @@ use Wikimedia\Parsoid\Config\SiteConfig;
use Wikimedia\Rdbms\DatabaseFactory;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\Rdbms\LBFactorySingle;
+use Wikimedia\Rdbms\LoadBalancerDisabled;
use Wikimedia\RequestTimeout\CriticalSectionProvider;
use Wikimedia\Services\NoSuchServiceException;
use Wikimedia\Services\SalvageableService;
@@ -229,6 +232,12 @@ class MediaWikiServices extends ServiceContainer {
private static $instance = null;
/**
+ * @see disableStorage()
+ * @var bool
+ */
+ private bool $storageDisabled = false;
+
+ /**
* Allows a global service container instance to exist.
*
* This should be called only after configuration settings have been read and extensions
@@ -450,10 +459,27 @@ class MediaWikiServices extends ServiceContainer {
/**
* Disables all storage layer services. After calling this, any attempt to access the
+ * storage layer will result in an error.
+ *
+ * @since 1.28
+ * @deprecated since 1.40, use disableStorage() instead.
+ *
+ * @warning This is intended for extreme situations, see the documentation of disableStorage() for details.
+ *
+ * @see resetGlobalInstance()
+ * @see resetChildProcessServices()
+ */
+ public static function disableStorageBackend() {
+ $services = self::getInstance();
+ $services->disableStorage();
+ }
+
+ /**
+ * Disables all storage layer services. After calling this, any attempt to access the
* storage layer will result in an error. Use resetGlobalInstance() to restore normal
* operation.
*
- * @since 1.28
+ * @since 1.40
*
* @warning This is intended for extreme situations only and should never be used
* while serving normal web requests. Legitimate use cases for this method include
@@ -463,20 +489,69 @@ class MediaWikiServices extends ServiceContainer {
* @see resetGlobalInstance()
* @see resetChildProcessServices()
*/
- public static function disableStorageBackend() {
- // TODO: also disable some Caches, JobQueues, etc
- $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ];
- $services = self::getInstance();
-
- foreach ( $destroy as $name ) {
- $services->disableService( $name );
+ public function disableStorage() {
+ if ( $this->storageDisabled ) {
+ return;
}
+ $this->redefineService(
+ 'DBLoadBalancer',
+ static function ( MediaWikiServices $services ) {
+ return new LoadBalancerDisabled();
+ }
+ );
+
+ $this->redefineService(
+ 'DBLoadBalancerFactory',
+ static function ( MediaWikiServices $services ) {
+ return LBFactorySingle::newDisabled();
+ }
+ );
+
+ $this->redefineService(
+ 'InterwikiLookup',
+ static function ( MediaWikiServices $services ) {
+ return new NullInterwikiLookup();
+ }
+ );
+
+ $this->redefineService(
+ 'UserOptionsLookup',
+ static function ( MediaWikiServices $services ) {
+ return $services->get( '_DefaultOptionsLookup' );
+ }
+ );
+
+ $this->addServiceManipulator(
+ 'LocalisationCache',
+ static function ( LocalisationCache $cache ) {
+ $cache->disableBackend();
+ }
+ );
+
+ $this->addServiceManipulator(
+ 'MessageCache',
+ static function ( MessageCache $cache ) {
+ $cache->disable();
+ }
+ );
+
ObjectCache::clear();
+
+ $this->storageDisabled = true;
+ }
+
+ /**
+ * Returns true if disableStorage() has been called on this MediaWikiServices instance.
+ *
+ * @return bool
+ */
+ public function isStorageDisabled(): bool {
+ return $this->storageDisabled;
}
/**
- * Resets any services that may have become stale after a child process
+ * Resets any services that may have become stale after a child processö
* returns from after pcntl_fork(). It's also safe, but generally unnecessary,
* to call this method from the parent process.
*
@@ -485,7 +560,7 @@ class MediaWikiServices extends ServiceContainer {
* @note This is intended for use in the context of process forking only!
*
* @see resetGlobalInstance()
- * @see disableStorageBackend()
+ * @see disableStorage()
*/
public static function resetChildProcessServices() {
// NOTE: for now, just reset everything. Since we don't know the interdependencies
@@ -547,7 +622,7 @@ class MediaWikiServices extends ServiceContainer {
*
* @see resetGlobalInstance()
* @see forceGlobalInstance()
- * @see disableStorageBackend()
+ * @see disableStorage()
*/
public static function failIfResetNotAllowed( $method ) {
if ( !defined( 'MW_PHPUNIT_TEST' )
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index 2b2847cd82af..d64b6c17b8c6 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -413,10 +413,8 @@ abstract class Installer {
$defaultConfig = new GlobalVarConfig(); // all the defaults from config-schema.yaml.
$installerConfig = self::getInstallerConfig( $defaultConfig );
- $this->resetMediaWikiServices( $installerConfig );
-
// Disable all storage services, since we don't have any configuration yet!
- MediaWikiServices::disableStorageBackend();
+ $this->resetMediaWikiServices( $installerConfig, [], true );
$this->settings = $this->getDefaultSettings();
@@ -465,31 +463,48 @@ abstract class Installer {
* @param array $serviceOverrides Service definition overrides. Values can be null to
* disable specific overrides that would be applied per default, namely
* 'InterwikiLookup' and 'UserOptionsLookup'.
+ * @param bool $disableStorage Whether MediaWikiServices::disableStorage() should be called.
*
* @return MediaWikiServices
*/
- public function resetMediaWikiServices( Config $installerConfig = null, $serviceOverrides = [] ) {
+ public function resetMediaWikiServices(
+ Config $installerConfig = null,
+ $serviceOverrides = [],
+ bool $disableStorage = false
+ ) {
global $wgObjectCaches, $wgLang;
- $serviceOverrides += [
- // Disable interwiki lookup, to avoid database access during parses
- 'InterwikiLookup' => static function () {
- return new NullInterwikiLookup();
- },
-
- // Disable user options database fetching, only rely on default options.
- 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
- return $services->get( '_DefaultOptionsLookup' );
- }
- ];
-
- $lang = $this->getVar( '_UserLang', 'en' );
-
- // Reset all services and inject config overrides
+ // Reset all services and inject config overrides.
+ // NOTE: This will reset existing instances, but not previous wiring overrides!
MediaWikiServices::resetGlobalInstance( $installerConfig );
$mwServices = MediaWikiServices::getInstance();
+ if ( $disableStorage ) {
+ $mwServices->disableStorage();
+ } else {
+ // Default to partially disabling services.
+
+ $serviceOverrides += [
+ // Disable interwiki lookup, to avoid database access during parses
+ 'InterwikiLookup' => static function () {
+ return new NullInterwikiLookup();
+ },
+
+ // Disable user options database fetching, only rely on default options.
+ 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
+ return $services->get( '_DefaultOptionsLookup' );
+ },
+
+ // Restore to default wiring, in case it was overwritten by disableStorage()
+ 'DBLoadBalancer' => static function ( MediaWikiServices $services ) {
+ return $services->getDBLoadBalancerFactory()->getMainLB();
+ },
+ ];
+ }
+
+ $lang = $this->getVar( '_UserLang', 'en' );
+
foreach ( $serviceOverrides as $name => $callback ) {
// Skip if the caller set $callback to null
// to suppress default overrides.
diff --git a/includes/libs/rdbms/lbfactory/LBFactorySingle.php b/includes/libs/rdbms/lbfactory/LBFactorySingle.php
index 7a5380643ce5..2e73ee541f66 100644
--- a/includes/libs/rdbms/lbfactory/LBFactorySingle.php
+++ b/includes/libs/rdbms/lbfactory/LBFactorySingle.php
@@ -34,20 +34,26 @@ class LBFactorySingle extends LBFactory {
/**
* You probably want to use {@link newFromConnection} instead.
*
- * @param array $conf An associative array with one member:
- * - connection: The IDatabase connection object
+ * @param array $conf An associative array containing one of the following:
+ * - connection: The IDatabase connection object to use
+ * - lb: The ILoadBalancer object to use
*/
public function __construct( array $conf ) {
parent::__construct( $conf );
- if ( !isset( $conf['connection'] ) ) {
- throw new InvalidArgumentException( "Missing 'connection' argument." );
+ if ( isset( $conf['lb'] ) ) {
+ $lb = $conf['lb'];
+ } else {
+ if ( !isset( $conf['connection'] ) ) {
+ throw new InvalidArgumentException( "Missing 'connection' argument." );
+ }
+
+ $lb = new LoadBalancerSingle( array_merge(
+ $this->baseLoadBalancerParams(),
+ $conf
+ ) );
}
- $lb = new LoadBalancerSingle( array_merge(
- $this->baseLoadBalancerParams(),
- $conf
- ) );
$this->initLoadBalancer( $lb );
$this->lb = $lb;
@@ -67,6 +73,19 @@ class LBFactorySingle extends LBFactory {
) );
}
+ /**
+ * @param array $params Parameter map to LBFactorySingle::__construct()
+ * and LoadBalancerDisabled::__construct()
+ * @return LBFactorySingle
+ * @since 1.40
+ */
+ public static function newDisabled( array $params = [] ) {
+ return new static( array_merge(
+ [ 'lb' => new LoadBalancerDisabled( $params ) ],
+ $params
+ ) );
+ }
+
public function newMainLB( $domain = false ): ILoadBalancerForOwner {
// @phan-suppress-previous-line PhanPluginNeverReturnMethod
throw new BadMethodCallException( "Method is not supported." );
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancerDisabled.php b/includes/libs/rdbms/loadbalancer/LoadBalancerDisabled.php
new file mode 100644
index 000000000000..b90ea4085249
--- /dev/null
+++ b/includes/libs/rdbms/loadbalancer/LoadBalancerDisabled.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * 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
+ */
+namespace Wikimedia\Rdbms;
+
+use RuntimeException;
+
+/**
+ * Trivial LoadBalancer that always fails.
+ * Useful when running code with no config file present,
+ * e.g. during the installation process.
+ *
+ * @since 1.40
+ *
+ * @ingroup Database
+ */
+class LoadBalancerDisabled extends LoadBalancer {
+
+ public function __construct( $params = [] ) {
+ parent::__construct( [
+ 'servers' => [ [
+ 'type' => 'disabled',
+ 'host' => '(disabled)',
+ 'dbname' => null,
+ 'load' => 1,
+ ] ],
+ 'trxProfiler' => $params['trxProfiler'] ?? null,
+ 'srvCache' => $params['srvCache'] ?? null,
+ 'wanCache' => $params['wanCache'] ?? null,
+ 'localDomain' => $params['localDomain'] ?? '(disabled)',
+ 'readOnlyReason' => $params['readOnlyReason'] ?? false,
+ 'clusterName' => $params['clusterName'] ?? null,
+ ] );
+ }
+
+ /**
+ * @param int $i
+ * @param DatabaseDomain $domain
+ * @param array $lbInfo
+ *
+ * @return never
+ */
+ protected function reallyOpenConnection( $i, DatabaseDomain $domain, array $lbInfo ) {
+ throw new RuntimeException( 'Database backend disabled' );
+ }
+
+ /**
+ * @param int $i Specific (overrides $groups) or virtual (DB_PRIMARY/DB_REPLICA) server index
+ * @param string[]|string $groups Query group(s) in preference order; [] for the default group
+ * @param string|false $domain DB domain ID or false for the local domain
+ * @param int $flags Bitfield of CONN_* class constants
+ *
+ * @return never
+ */
+ public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
+ throw new RuntimeException( 'Database backend disabled' );
+ }
+
+ /**
+ * @internal Only to be used by DBConnRef
+ * @param int $i Specific (overrides $groups) or virtual (DB_PRIMARY/DB_REPLICA) server index
+ * @param string[]|string $groups Query group(s) in preference order; [] for the default group
+ * @param string|false $domain DB domain ID or false for the local domain
+ * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
+ * @return never
+ */
+ public function getConnectionInternal( $i, $groups = [], $domain = false, $flags = 0 ): IDatabase {
+ throw new RuntimeException( 'Database backend disabled' );
+ }
+
+ /**
+ * @param int $i Specific (overrides $groups) or virtual (DB_PRIMARY/DB_REPLICA) server index
+ * @param string[]|string $groups Query group(s) in preference order; [] for the default group
+ * @param string|false $domain DB domain ID or false for the local domain
+ * @param int $flags Bitfield of CONN_* class constants
+ *
+ * @return never
+ */
+ public function getConnectionRef( $i, $groups = [], $domain = false, $flags = 0 ): IDatabase {
+ throw new RuntimeException( 'Database backend disabled' );
+ }
+
+ /**
+ * @param int $i Specific (overrides $groups) or virtual (DB_PRIMARY/DB_REPLICA) server index
+ * @param string[]|string $groups Query group(s) in preference order; [] for the default group
+ * @param string|false $domain DB domain ID or false for the local domain
+ * @param int $flags Bitfield of CONN_* class constants
+ *
+ * @return never
+ */
+ public function getMaintenanceConnectionRef( $i, $groups = [], $domain = false, $flags = 0 ): DBConnRef {
+ throw new RuntimeException( 'Database backend disabled' );
+ }
+
+ public function reuseConnection( IDatabase $conn ) {
+ // noop
+ }
+
+}
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index b9beeb54f959..b6505ee7088f 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -246,9 +246,14 @@ class ObjectCache {
}
}
- if ( MediaWikiServices::getInstance()->isServiceDisabled( 'DBLoadBalancer' ) ) {
- // The LoadBalancer is disabled, probably because
- // MediaWikiServices::disableStorageBackend was called.
+ $services = MediaWikiServices::getInstance();
+
+ if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
+ // The DBLoadBalancer service is disabled, so we can't use the database!
+ $candidate = CACHE_NONE;
+ } elseif ( $services->isStorageDisabled() ) {
+ // Storage services are disabled because MediaWikiServices::disableStorage()
+ // was called. This is typically the case during installation.
$candidate = CACHE_NONE;
} else {
$candidate = CACHE_DB;