Allow resources to be salvaged across service resets.
[lhc/web/wiklou.git] / includes / MediaWikiServices.php
index 6c650aa..3d48419 100644 (file)
@@ -4,11 +4,13 @@ namespace MediaWiki;
 use Config;
 use ConfigFactory;
 use EventRelayerGroup;
+use GenderCache;
 use GlobalVarConfig;
 use Hooks;
 use LBFactory;
 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
 use LoadBalancer;
+use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceContainer;
 use MWException;
 use ResourceLoader;
@@ -19,6 +21,8 @@ use SiteLookup;
 use SiteStore;
 use WatchedItemStore;
 use SkinFactory;
+use TitleFormatter;
+use TitleParser;
 
 /**
  * Service locator for MediaWiki core services.
@@ -66,6 +70,8 @@ class MediaWikiServices extends ServiceContainer {
        /**
         * Returns the global default instance of the top level service locator.
         *
+        * @since 1.27
+        *
         * The default instance is initialized using the service instantiator functions
         * defined in ServiceWiring.php.
         *
@@ -82,7 +88,7 @@ class MediaWikiServices extends ServiceContainer {
                        // even if it's just a file name or database credentials to load
                        // configuration from.
                        $bootstrapConfig = new GlobalVarConfig();
-                       self::$instance = self::newInstance( $bootstrapConfig );
+                       self::$instance = self::newInstance( $bootstrapConfig, 'load' );
                }
 
                return self::$instance;
@@ -91,6 +97,8 @@ class MediaWikiServices extends ServiceContainer {
        /**
         * Replaces the global MediaWikiServices instance.
         *
+        * @since 1.28
+        *
         * @note This is for use in PHPUnit tests only!
         *
         * @throws MWException if called outside of PHPUnit tests.
@@ -113,13 +121,15 @@ class MediaWikiServices extends ServiceContainer {
        /**
         * Creates a new instance of MediaWikiServices and sets it as the global default
         * instance. getInstance() will return a different MediaWikiServices object
-        * after every call to resetGlobalServiceLocator().
+        * after every call to resetGlobalInstance().
+        *
+        * @since 1.28
         *
         * @warning This should not be used during normal operation. It is intended for use
         * when the configuration has changed significantly since bootstrap time, e.g.
         * during the installation process or during testing.
         *
-        * @warning Calling resetGlobalServiceLocator() may leave the application in an inconsistent
+        * @warning Calling resetGlobalInstance() may leave the application in an inconsistent
         * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
         * any of the services managed by MediaWikiServices exist. If any service objects
         * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
@@ -140,11 +150,14 @@ class MediaWikiServices extends ServiceContainer {
         *        was no previous instance, a new GlobalVarConfig object will be used to
         *        bootstrap the services.
         *
+        * @param string $quick Set this to "quick" to allow expensive resources to be re-used.
+        * See SalvageableService for details.
+        *
         * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
         *         Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
         *          is defined).
         */
-       public static function resetGlobalInstance( Config $bootstrapConfig = null ) {
+       public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) {
                if ( self::$instance === null ) {
                        // no global instance yet, nothing to reset
                        return;
@@ -156,9 +169,38 @@ class MediaWikiServices extends ServiceContainer {
                        $bootstrapConfig = self::$instance->getBootstrapConfig();
                }
 
-               self::$instance->destroy();
+               $oldInstance = self::$instance;
 
                self::$instance = self::newInstance( $bootstrapConfig );
+               self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] );
+
+               if ( $quick === 'quick' ) {
+                       self::$instance->salvage( $oldInstance );
+               } else {
+                       $oldInstance->destroy();
+               }
+
+       }
+
+       /**
+        * Salvages the state of any salvageable service instances in $other.
+        *
+        * @note $other will have been destroyed when salvage() returns.
+        *
+        * @param MediaWikiServices $other
+        */
+       private function salvage( self $other ) {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $oldService = $other->peekService( $name );
+
+                       if ( $oldService instanceof SalvageableService ) {
+                               /** @var SalvageableService $newService */
+                               $newService = $this->getService( $name );
+                               $newService->salvage( $oldService );
+                       }
+               }
+
+               $other->destroy();
        }
 
        /**
@@ -167,21 +209,23 @@ class MediaWikiServices extends ServiceContainer {
         * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
         *
         * @param Config|null $bootstrapConfig The Config object to be registered as the
-        *        'BootstrapConfig' service. This has to contain at least the information
-        *        needed to set up the 'ConfigFactory' service. If not provided, any call
-        *        to getBootstrapConfig(), getConfigFactory, or getMainConfig will fail.
-        *        A MediaWikiServices instance without access to configuration is called
-        *        "primordial".
+        *        'BootstrapConfig' service.
+        *
+        * @param string $loadWiring set this to 'load' to load the wiring files specified
+        *        in the 'ServiceWiringFiles' setting in $bootstrapConfig.
         *
         * @return MediaWikiServices
         * @throws MWException
+        * @throws \FatalError
         */
-       private static function newInstance( Config $bootstrapConfig ) {
+       private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) {
                $instance = new self( $bootstrapConfig );
 
                // Load the default wiring from the specified files.
-               $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
-               $instance->loadWiringFiles( $wiringFiles );
+               if ( $loadWiring === 'load' ) {
+                       $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
+                       $instance->loadWiringFiles( $wiringFiles );
+               }
 
                // Provide a traditional hook point to allow extensions to configure services.
                Hooks::run( 'MediaWikiServices', [ $instance ] );
@@ -194,6 +238,8 @@ class MediaWikiServices extends ServiceContainer {
         * storage layer will result in an error. Use resetGlobalInstance() to restore normal
         * operation.
         *
+        * @since 1.28
+        *
         * @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
         * the installation process. Test fixtures may also use this, if the fixture relies
@@ -217,6 +263,8 @@ class MediaWikiServices extends ServiceContainer {
         * returns from after pcntl_fork(). It's also safe, but generally unnecessary,
         * to call this method from the parent process.
         *
+        * @since 1.28
+        *
         * @note This is intended for use in the context of process forking only!
         *
         * @see resetGlobalInstance()
@@ -235,6 +283,8 @@ class MediaWikiServices extends ServiceContainer {
        /**
         * Resets the given service for testing purposes.
         *
+        * @since 1.28
+        *
         * @warning This is generally unsafe! Other services may still retain references
         * to the stale service instance, leading to failures and inconsistencies. Subclasses
         * may use this method to reset specific services under specific instances, but
@@ -264,6 +314,8 @@ class MediaWikiServices extends ServiceContainer {
         * resetting of global services is allowed. In general, services should not be reset
         * individually, since that may introduce inconsistencies.
         *
+        * @since 1.28
+        *
         * This method will throw an exception if:
         *
         * - self::$resetInProgress is false (to allow all services to be reset together
@@ -321,6 +373,7 @@ class MediaWikiServices extends ServiceContainer {
         * when creating the MainConfig service. Application logic should
         * use getMainConfig() to get a Config instances.
         *
+        * @since 1.27
         * @return Config
         */
        public function getBootstrapConfig() {
@@ -328,6 +381,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return ConfigFactory
         */
        public function getConfigFactory() {
@@ -338,6 +392,7 @@ class MediaWikiServices extends ServiceContainer {
         * Returns the Config object that provides configuration for MediaWiki core.
         * This may or may not be the same object that is returned by getBootstrapConfig().
         *
+        * @since 1.27
         * @return Config
         */
        public function getMainConfig() {
@@ -345,6 +400,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SiteLookup
         */
        public function getSiteLookup() {
@@ -352,6 +408,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SiteStore
         */
        public function getSiteStore() {
@@ -359,6 +416,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return StatsdDataFactory
         */
        public function getStatsdDataFactory() {
@@ -366,6 +424,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return EventRelayerGroup
         */
        public function getEventRelayerGroup() {
@@ -373,6 +432,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngine
         */
        public function newSearchEngine() {
@@ -381,6 +441,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngineFactory
         */
        public function getSearchEngineFactory() {
@@ -388,6 +449,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SearchEngineConfig
         */
        public function getSearchEngineConfig() {
@@ -395,6 +457,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.27
         * @return SkinFactory
         */
        public function getSkinFactory() {
@@ -402,6 +465,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.28
         * @return LBFactory
         */
        public function getDBLoadBalancerFactory() {
@@ -409,6 +473,7 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.28
         * @return LoadBalancer The main DB load balancer for the local wiki.
         */
        public function getDBLoadBalancer() {
@@ -416,12 +481,36 @@ class MediaWikiServices extends ServiceContainer {
        }
 
        /**
+        * @since 1.28
         * @return WatchedItemStore
         */
        public function getWatchedItemStore() {
                return $this->getService( 'WatchedItemStore' );
        }
 
+       /**
+        * @since 1.28
+        * @return GenderCache
+        */
+       public function getGenderCache() {
+               return $this->getService( 'GenderCache' );
+       }
+       /**
+        * @since 1.28
+        * @return TitleFormatter
+        */
+       public function getTitleFormatter() {
+               return $this->getService( 'TitleFormatter' );
+       }
+
+       /**
+        * @since 1.28
+        * @return TitleParser
+        */
+       public function getTitleParser() {
+               return $this->getService( 'TitleParser' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in