Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 90 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
UpdateSpecialPages | |
0.00% |
0 / 90 |
|
0.00% |
0 / 4 |
702 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
240 | |||
reopenAndWaitForReplicas | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
doSpecialPageCacheUpdates | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | /** |
3 | * Update for cached special pages. |
4 | * Run this script periodically if you have miser mode enabled. |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with this program; if not, write to the Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | * http://www.gnu.org/copyleft/gpl.html |
20 | * |
21 | * @file |
22 | * @ingroup Maintenance |
23 | */ |
24 | |
25 | // @codeCoverageIgnoreStart |
26 | require_once __DIR__ . '/Maintenance.php'; |
27 | // @codeCoverageIgnoreEnd |
28 | |
29 | use MediaWiki\MainConfigNames; |
30 | use MediaWiki\Maintenance\Maintenance; |
31 | use MediaWiki\SpecialPage\QueryPage; |
32 | |
33 | /** |
34 | * Maintenance script to update cached special pages. |
35 | * |
36 | * @ingroup Maintenance |
37 | */ |
38 | class UpdateSpecialPages extends Maintenance { |
39 | public function __construct() { |
40 | parent::__construct(); |
41 | $this->addOption( 'list', 'List special page names' ); |
42 | $this->addOption( 'only', 'Only update "page"; case sensitive, ' . |
43 | 'check correct case by calling this script with --list. ' . |
44 | 'Ex: --only=BrokenRedirects', false, true ); |
45 | $this->addOption( 'override', 'Also update pages that have updates disabled' ); |
46 | } |
47 | |
48 | public function execute() { |
49 | $dbw = $this->getPrimaryDB(); |
50 | $config = $this->getConfig(); |
51 | $specialPageFactory = $this->getServiceContainer()->getSpecialPageFactory(); |
52 | |
53 | $this->doSpecialPageCacheUpdates( $dbw ); |
54 | |
55 | $queryCacheLimit = (int)$config->get( MainConfigNames::QueryCacheLimit ); |
56 | $disabledQueryPages = QueryPage::getDisabledQueryPages( $config ); |
57 | foreach ( QueryPage::getPages() as $page ) { |
58 | [ , $special ] = $page; |
59 | $limit = $page[2] ?? $queryCacheLimit; |
60 | |
61 | # --list : just show the name of pages |
62 | if ( $this->hasOption( 'list' ) ) { |
63 | $this->output( "$special [QueryPage]\n" ); |
64 | continue; |
65 | } |
66 | |
67 | if ( !$this->hasOption( 'override' ) |
68 | && isset( $disabledQueryPages[$special] ) |
69 | ) { |
70 | $this->output( sprintf( "%-30s [QueryPage] disabled\n", $special ) ); |
71 | continue; |
72 | } |
73 | |
74 | $specialObj = $specialPageFactory->getPage( $special ); |
75 | if ( !$specialObj ) { |
76 | $this->output( "No such special page: $special\n" ); |
77 | return; |
78 | } |
79 | if ( $specialObj instanceof QueryPage ) { |
80 | $queryPage = $specialObj; |
81 | } else { |
82 | $class = get_class( $specialObj ); |
83 | $this->fatalError( "$class is not an instance of QueryPage.\n" ); |
84 | } |
85 | |
86 | if ( !$this->hasOption( 'only' ) || $this->getOption( 'only' ) === $queryPage->getName() ) { |
87 | $this->output( sprintf( '%-30s [QueryPage] ', $special ) ); |
88 | if ( $queryPage->isExpensive() ) { |
89 | $t1 = microtime( true ); |
90 | # Do the query |
91 | $num = $queryPage->recache( $limit ); |
92 | $t2 = microtime( true ); |
93 | if ( $num === false ) { |
94 | $this->output( "FAILED: database error\n" ); |
95 | } else { |
96 | $this->output( "got $num rows in " ); |
97 | |
98 | $elapsed = $t2 - $t1; |
99 | $hours = intval( $elapsed / 3600 ); |
100 | $minutes = intval( (int)$elapsed % 3600 / 60 ); |
101 | $seconds = $elapsed - $hours * 3600 - $minutes * 60; |
102 | if ( $hours ) { |
103 | $this->output( $hours . 'h ' ); |
104 | } |
105 | if ( $minutes ) { |
106 | $this->output( $minutes . 'm ' ); |
107 | } |
108 | $this->output( sprintf( "%.2fs\n", $seconds ) ); |
109 | } |
110 | # Reopen any connections that have closed |
111 | $this->reopenAndWaitForReplicas(); |
112 | } else { |
113 | // Check if this page was expensive before and now cheap |
114 | $cached = $queryPage->getCachedTimestamp(); |
115 | if ( $cached ) { |
116 | $queryPage->deleteAllCachedData(); |
117 | $this->reopenAndWaitForReplicas(); |
118 | $this->output( "cheap, but deleted cached data\n" ); |
119 | } else { |
120 | $this->output( "cheap, skipped\n" ); |
121 | } |
122 | } |
123 | if ( $this->hasOption( 'only' ) ) { |
124 | break; |
125 | } |
126 | } |
127 | } |
128 | } |
129 | |
130 | /** |
131 | * Re-open any closed db connection, and wait for replicas |
132 | * |
133 | * Queries that take a really long time, might cause the |
134 | * mysql connection to "go away" |
135 | */ |
136 | private function reopenAndWaitForReplicas() { |
137 | $lbFactory = $this->getServiceContainer()->getDBLoadBalancerFactory(); |
138 | $lb = $lbFactory->getMainLB(); |
139 | if ( !$lb->pingAll() ) { |
140 | $this->output( "\n" ); |
141 | do { |
142 | $this->error( "Connection failed, reconnecting in 10 seconds..." ); |
143 | sleep( 10 ); |
144 | $this->waitForReplication(); |
145 | } while ( !$lb->pingAll() ); |
146 | $this->output( "Reconnected\n\n" ); |
147 | } |
148 | $this->waitForReplication(); |
149 | } |
150 | |
151 | public function doSpecialPageCacheUpdates( $dbw ) { |
152 | foreach ( $this->getConfig()->get( MainConfigNames::SpecialPageCacheUpdates ) as $special => $call ) { |
153 | # --list : just show the name of pages |
154 | if ( $this->hasOption( 'list' ) ) { |
155 | $this->output( "$special [callback]\n" ); |
156 | continue; |
157 | } |
158 | |
159 | if ( !$this->hasOption( 'only' ) || $this->getOption( 'only' ) === $special ) { |
160 | if ( !is_callable( $call ) ) { |
161 | $this->error( "Uncallable function $call!" ); |
162 | continue; |
163 | } |
164 | $this->output( sprintf( '%-30s [callback] ', $special ) ); |
165 | $t1 = microtime( true ); |
166 | $call( $dbw ); |
167 | $t2 = microtime( true ); |
168 | |
169 | $this->output( "completed in " ); |
170 | $elapsed = $t2 - $t1; |
171 | $hours = intval( $elapsed / 3600 ); |
172 | $minutes = intval( (int)$elapsed % 3600 / 60 ); |
173 | $seconds = $elapsed - $hours * 3600 - $minutes * 60; |
174 | if ( $hours ) { |
175 | $this->output( $hours . 'h ' ); |
176 | } |
177 | if ( $minutes ) { |
178 | $this->output( $minutes . 'm ' ); |
179 | } |
180 | $this->output( sprintf( "%.2fs\n", $seconds ) ); |
181 | # Wait for the replica DB to catch up |
182 | $this->reopenAndWaitForReplicas(); |
183 | } |
184 | } |
185 | } |
186 | } |
187 | |
188 | // @codeCoverageIgnoreStart |
189 | $maintClass = UpdateSpecialPages::class; |
190 | require_once RUN_MAINTENANCE_IF_MAIN; |
191 | // @codeCoverageIgnoreEnd |