MediaWiki REL1_37
rebuildLocalisationCache.php
Go to the documentation of this file.
1<?php
2
35
36require_once __DIR__ . '/Maintenance.php';
37
44 public function __construct() {
45 parent::__construct();
46 $this->addDescription( 'Rebuild the localisation cache' );
47 $this->addOption( 'force', 'Rebuild all files, even ones not out of date' );
48 $this->addOption( 'threads', 'Fork more than one thread', false, true );
49 $this->addOption( 'outdir', 'Override the output directory (normally $wgCacheDirectory)',
50 false, true );
51 $this->addOption( 'lang', 'Only rebuild these languages, comma separated.',
52 false, true );
53 $this->addOption(
54 'store-class',
55 'Override the LC store class (normally $wgLocalisationCacheConf[\'storeClass\'])',
56 false,
57 true
58 );
59 $this->addOption(
60 'no-database',
61 'EXPERIMENTAL: Disable the database backend. Setting this option will result in an error ' .
62 'if you have extensions or use site configuration that need the database. This is an ' .
63 'experimental feature to allow offline building of the localisation cache. Known limitations:' .
64 "\n" .
65 '* Incompatible with LCStoreDB, which always requires a database. ' . "\n" .
66 '* The message purge may require a database. See --skip-message-purge.'
67 );
68 // T237148: The Gadget extension (bundled with MediaWiki by default) requires a database`
69 // connection to register its modules for MessageBlobStore.
70 $this->addOption(
71 'skip-message-purge',
72 'Skip purging of MessageBlobStore. The purge operation may require a database, depending ' .
73 'on the configuration and extensions on this wiki. If skipping the purge now, you need to ' .
74 'run purgeMessageBlobStore.php shortly after deployment.'
75 );
76 }
77
78 public function finalSetup() {
79 # This script needs to be run to build the initial l10n cache. But if
80 # $wgLanguageCode is not 'en', it won't be able to run because there is
81 # no l10n cache. Break the cycle by forcing $wgLanguageCode = 'en'.
82 global $wgLanguageCode;
83 $wgLanguageCode = 'en';
84 parent::finalSetup();
85 }
86
87 public function execute() {
89
90 $force = $this->hasOption( 'force' );
91 $threads = $this->getOption( 'threads', 1 );
92 if ( $threads < 1 || $threads != intval( $threads ) ) {
93 $this->output( "Invalid thread count specified; running single-threaded.\n" );
94 $threads = 1;
95 }
96 if ( $threads > 1 && wfIsWindows() ) {
97 $this->output( "Threaded rebuild is not supported on Windows; running single-threaded.\n" );
98 $threads = 1;
99 }
100 if ( $threads > 1 && !function_exists( 'pcntl_fork' ) ) {
101 $this->output( "PHP pcntl extension is not present; running single-threaded.\n" );
102 $threads = 1;
103 }
104
106 // Allow fallbacks to create CDB files
107 $conf['manualRecache'] = false;
108 $conf['forceRecache'] = $force || !empty( $conf['forceRecache'] );
109 if ( $this->hasOption( 'outdir' ) ) {
110 $conf['storeDirectory'] = $this->getOption( 'outdir' );
111 }
112
113 if ( $this->hasOption( 'store-class' ) ) {
114 $conf['storeClass'] = $this->getOption( 'store-class' );
115 }
116
117 // XXX Copy-pasted from ServiceWiring.php. Do we need a factory for this one caller?
118 $services = MediaWikiServices::getInstance();
120 new ServiceOptions(
121 LocalisationCache::CONSTRUCTOR_OPTIONS,
122 $conf,
123 $services->getMainConfig()
124 ),
125 LocalisationCache::getStoreFromConf( $conf, $wgCacheDirectory ),
126 LoggerFactory::getInstance( 'localisation' ),
127 $this->hasOption( 'skip-message-purge' ) ? [] :
128 [ static function () use ( $services ) {
129 MessageBlobStore::clearGlobalCacheEntry( $services->getMainWANObjectCache() );
130 } ],
131 $services->getLanguageNameUtils(),
132 $services->getHookContainer()
133 );
134
135 $allCodes = array_keys( $services
136 ->getLanguageNameUtils()
137 ->getLanguageNames( null, 'mwfile' ) );
138 if ( $this->hasOption( 'lang' ) ) {
139 # Validate requested languages
140 $codes = array_intersect( $allCodes,
141 explode( ',', $this->getOption( 'lang' ) ) );
142 # Bailed out if nothing is left
143 if ( count( $codes ) == 0 ) {
144 $this->fatalError( 'None of the languages specified exists.' );
145 }
146 } else {
147 # By default get all languages
148 $codes = $allCodes;
149 }
150 sort( $codes );
151
152 // Initialise and split into chunks
153 $numRebuilt = 0;
154 $total = count( $codes );
155 $chunks = array_chunk( $codes, ceil( count( $codes ) / $threads ) );
156 $pids = [];
157 $parentStatus = 0;
158 foreach ( $chunks as $codes ) {
159 // Do not fork for only one thread
160 $pid = ( $threads > 1 ) ? pcntl_fork() : -1;
161
162 if ( $pid === 0 ) {
163 // Child, reseed because there is no bug in PHP:
164 // https://bugs.php.net/bug.php?id=42465
165 mt_srand( getmypid() );
166
167 $this->doRebuild( $codes, $lc, $force );
168 return;
169 } elseif ( $pid === -1 ) {
170 // Fork failed or one thread, do it serialized
171 $numRebuilt += $this->doRebuild( $codes, $lc, $force );
172 } else {
173 // Main thread
174 $pids[] = $pid;
175 }
176 }
177 // Wait for all children
178 foreach ( $pids as $pid ) {
179 $status = 0;
180 pcntl_waitpid( $pid, $status );
181
182 if ( pcntl_wifexited( $status ) ) {
183 $code = pcntl_wexitstatus( $status );
184 if ( $code ) {
185 $this->output( "Pid $pid exited with status $code !!\n" );
186 }
187 // Mush all child statuses into a single value in the parent.
188 $parentStatus |= $code;
189 } elseif ( pcntl_wifsignaled( $status ) ) {
190 $signum = pcntl_wtermsig( $status );
191 $this->output( "Pid $pid terminated by signal $signum !!\n" );
192 $parentStatus |= 1;
193 }
194 }
195
196 if ( !$pids ) {
197 $this->output( "$numRebuilt languages rebuilt out of $total\n" );
198 if ( $numRebuilt === 0 ) {
199 $this->output( "Use --force to rebuild the caches which are still fresh.\n" );
200 }
201 }
202 if ( $parentStatus ) {
203 $this->fatalError( 'Failed.', $parentStatus );
204 }
205 }
206
215 private function doRebuild( $codes, $lc, $force ) {
216 $numRebuilt = 0;
217 foreach ( $codes as $code ) {
218 if ( $force || $lc->isExpired( $code ) ) {
219 $this->output( "Rebuilding $code...\n" );
220 $lc->recache( $code );
221 $numRebuilt++;
222 }
223 }
224
225 return $numRebuilt;
226 }
227
229 public function getDbType() {
230 if ( $this->hasOption( 'no-database' ) ) {
232 }
233
234 return parent::getDbType();
235 }
236
242 public function setForce( $forced = true ) {
243 $this->mOptions['force'] = $forced;
244 }
245}
246
247$maintClass = RebuildLocalisationCache::class;
248require_once RUN_MAINTENANCE_IF_MAIN;
$wgLanguageCode
Site language code.
$wgCacheDirectory
Directory for caching data in the local filesystem.
$wgLocalisationCacheConf
Localisation cache configuration.
wfIsWindows()
Check if the operating system is Windows.
A localisation cache optimised for loading large amounts of data for many languages.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
const DB_NONE
Constants for DB access type.
output( $out, $channel=null)
Throw some output to the user.
hasOption( $name)
Checks to see if a particular option was set.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
A class for passing options to services.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static clearGlobalCacheEntry(WANObjectCache $cache)
Invalidate cache keys for all known modules.
Maintenance script to rebuild the localisation cache.
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
finalSetup()
Handle some last-minute setup here.
setForce( $forced=true)
Sets whether a run of this maintenance script has the force parameter set.
doRebuild( $codes, $lc, $force)
Helper function to rebuild list of languages codes.