Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
67.44% |
29 / 43 |
|
42.86% |
3 / 7 |
CRAP | |
0.00% |
0 / 1 |
PoolCounterWork | |
69.05% |
29 / 42 |
|
42.86% |
3 / 7 |
38.69 | |
0.00% |
0 / 1 |
__construct | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
1.04 | |||
doWork | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getCachedWork | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
error | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isFastStaleEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
logError | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
80.00% |
24 / 30 |
|
0.00% |
0 / 1 |
19.31 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\PoolCounter; |
22 | |
23 | use MediaWiki\MediaWikiServices; |
24 | use MediaWiki\Status\Status; |
25 | |
26 | /** |
27 | * Class for dealing with PoolCounters using class members |
28 | */ |
29 | abstract class PoolCounterWork { |
30 | /** @var string */ |
31 | protected $type = 'generic'; |
32 | /** @var bool */ |
33 | protected $cacheable = false; // does this override getCachedWork() ? |
34 | /** @var PoolCounter */ |
35 | private $poolCounter; |
36 | |
37 | /** |
38 | * @param string $type The class of actions to limit concurrency for (task type) |
39 | * @param string $key Key that identifies the queue this work is placed on |
40 | * @param PoolCounter|null $poolCounter |
41 | */ |
42 | public function __construct( string $type, string $key, ?PoolCounter $poolCounter = null ) { |
43 | $this->type = $type; |
44 | // MW >= 1.35 |
45 | $this->poolCounter = $poolCounter ?? |
46 | MediaWikiServices::getInstance()->getPoolCounterFactory()->create( $type, $key ); |
47 | } |
48 | |
49 | /** |
50 | * Actually perform the work, caching it if needed |
51 | * |
52 | * @return mixed|false Work result or false |
53 | */ |
54 | abstract public function doWork(); |
55 | |
56 | /** |
57 | * Retrieve the work from cache |
58 | * |
59 | * @return mixed|false Work result or false |
60 | */ |
61 | public function getCachedWork() { |
62 | return false; |
63 | } |
64 | |
65 | /** |
66 | * A work not so good (eg. expired one) but better than an error |
67 | * message. |
68 | * |
69 | * @param bool $fast True if PoolCounter is requesting a fast stale response (pre-wait) |
70 | * @return mixed|false Work result or false |
71 | */ |
72 | public function fallback( $fast ) { |
73 | return false; |
74 | } |
75 | |
76 | /** |
77 | * Do something with the error, like showing it to the user. |
78 | * |
79 | * @param Status $status |
80 | * @return mixed|false |
81 | */ |
82 | public function error( $status ) { |
83 | return false; |
84 | } |
85 | |
86 | /** |
87 | * Should fast stale mode be used? |
88 | * |
89 | * @return bool |
90 | */ |
91 | protected function isFastStaleEnabled() { |
92 | return $this->poolCounter->isFastStaleEnabled(); |
93 | } |
94 | |
95 | /** |
96 | * Log an error |
97 | * |
98 | * @param Status $status |
99 | * @return void |
100 | */ |
101 | public function logError( $status ) { |
102 | $key = $this->poolCounter->getKey(); |
103 | |
104 | $this->poolCounter->getLogger()->info( |
105 | "Pool key '$key' ({$this->type}): " . |
106 | $status->getMessage()->inLanguage( 'en' )->useDatabase( false )->text() |
107 | ); |
108 | } |
109 | |
110 | /** |
111 | * Get the result of the work (whatever it is), or the result of the error() function. |
112 | * |
113 | * This returns the result of the one of the following methods: |
114 | * |
115 | * - doWork(): Applies if the work is exclusive or no other process |
116 | * is doing it, and on the condition that either this process |
117 | * successfully entered the pool or the pool counter is down. |
118 | * - doCachedWork(): Applies if the work is cacheable and this blocked on another |
119 | * process which finished the work. |
120 | * - fallback(): Applies for all remaining cases. |
121 | * |
122 | * If these all return false, then the result of error() is returned. |
123 | * |
124 | * In slow-stale mode, these three methods are called in the sequence given above, and |
125 | * the first non-false response is used. This means in case of concurrent cache-miss requests |
126 | * for the same revision, later ones will load on DBs and other backend services, and wait for |
127 | * earlier requests to succeed and then read out their saved result. |
128 | * |
129 | * In fast-stale mode, if other requests hold doWork lock already, we call fallback() first |
130 | * to let it try to find an acceptable return value. If fallback() returns false, then we |
131 | * will wait for the doWork lock, as for slow stale mode, including potentially calling |
132 | * fallback() a second time. |
133 | * |
134 | * @param bool $skipcache |
135 | * @return mixed |
136 | */ |
137 | public function execute( $skipcache = false ) { |
138 | if ( !$this->cacheable || $skipcache ) { |
139 | $status = $this->poolCounter->acquireForMe(); |
140 | } else { |
141 | if ( $this->isFastStaleEnabled() ) { |
142 | // In fast stale mode, check for existing locks by acquiring lock with 0 timeout |
143 | $status = $this->poolCounter->acquireForAnyone( 0 ); |
144 | if ( $status->isOK() && $status->value === PoolCounter::TIMEOUT ) { |
145 | // Lock acquisition would block: try fallback |
146 | $staleResult = $this->fallback( true ); |
147 | if ( $staleResult !== false ) { |
148 | return $staleResult; |
149 | } |
150 | // No fallback available, so wait for the lock |
151 | $status = $this->poolCounter->acquireForAnyone(); |
152 | } // else behave as if $status were returned in slow mode |
153 | } else { |
154 | $status = $this->poolCounter->acquireForAnyone(); |
155 | } |
156 | } |
157 | |
158 | if ( !$status->isOK() ) { |
159 | // Respond gracefully to complete server breakage: just log it and do the work |
160 | $this->logError( $status ); |
161 | return $this->doWork(); |
162 | } |
163 | |
164 | switch ( $status->value ) { |
165 | case PoolCounter::LOCK_HELD: |
166 | // Better to ignore nesting pool counter limits than to fail. |
167 | // Assume that the outer pool limiting is reasonable enough. |
168 | /* no break */ |
169 | case PoolCounter::LOCKED: |
170 | try { |
171 | return $this->doWork(); |
172 | } finally { |
173 | $this->poolCounter->release(); |
174 | } |
175 | // no fall-through, because try returns or throws |
176 | case PoolCounter::DONE: |
177 | $result = $this->getCachedWork(); |
178 | if ( $result === false ) { |
179 | /* That someone else work didn't serve us. |
180 | * Acquire the lock for me |
181 | */ |
182 | return $this->execute( true ); |
183 | } |
184 | return $result; |
185 | |
186 | case PoolCounter::QUEUE_FULL: |
187 | case PoolCounter::TIMEOUT: |
188 | $result = $this->fallback( false ); |
189 | |
190 | if ( $result !== false ) { |
191 | return $result; |
192 | } |
193 | /* no break */ |
194 | |
195 | /* These two cases should never be hit... */ |
196 | case PoolCounter::ERROR: |
197 | default: |
198 | $errors = [ |
199 | PoolCounter::QUEUE_FULL => 'pool-queuefull', |
200 | PoolCounter::TIMEOUT => 'pool-timeout', |
201 | ]; |
202 | |
203 | $status = Status::newFatal( $errors[$status->value] ?? 'pool-errorunknown' ); |
204 | $this->logError( $status ); |
205 | return $this->error( $status ); |
206 | } |
207 | } |
208 | } |
209 | |
210 | /** @deprecated class alias since 1.42 */ |
211 | class_alias( PoolCounterWork::class, 'PoolCounterWork' ); |