Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / poolcounter / PoolCounterWork.php
blob9262ecd3965c46db62e06f4c020edf7a22439600
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.
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.
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
18 * @file
21 namespace MediaWiki\PoolCounter;
23 use MediaWiki\MediaWikiServices;
24 use MediaWiki\Status\Status;
26 /**
27 * Class for dealing with PoolCounters using class members
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;
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
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 );
49 /**
50 * Actually perform the work, caching it if needed
52 * @return mixed|false Work result or false
54 abstract public function doWork();
56 /**
57 * Retrieve the work from cache
59 * @return mixed|false Work result or false
61 public function getCachedWork() {
62 return false;
65 /**
66 * A work not so good (eg. expired one) but better than an error
67 * message.
69 * @param bool $fast True if PoolCounter is requesting a fast stale response (pre-wait)
70 * @return mixed|false Work result or false
72 public function fallback( $fast ) {
73 return false;
76 /**
77 * Do something with the error, like showing it to the user.
79 * @param Status $status
80 * @return mixed|false
82 public function error( $status ) {
83 return false;
86 /**
87 * Should fast stale mode be used?
89 * @return bool
91 protected function isFastStaleEnabled() {
92 return $this->poolCounter->isFastStaleEnabled();
95 /**
96 * Log an error
98 * @param Status $status
99 * @return void
101 public function logError( $status ) {
102 $key = $this->poolCounter->getKey();
104 $this->poolCounter->getLogger()->info(
105 "Pool key '$key' ({$this->type}): " .
106 $status->getMessage()->inLanguage( 'en' )->useDatabase( false )->text()
111 * Get the result of the work (whatever it is), or the result of the error() function.
113 * This returns the result of the one of the following methods:
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.
122 * If these all return false, then the result of error() is returned.
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.
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.
134 * @param bool $skipcache
135 * @return mixed
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;
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();
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();
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();
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
182 return $this->execute( true );
184 return $result;
186 case PoolCounter::QUEUE_FULL:
187 case PoolCounter::TIMEOUT:
188 $result = $this->fallback( false );
190 if ( $result !== false ) {
191 return $result;
193 /* no break */
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',
203 $status = Status::newFatal( $errors[$status->value] ?? 'pool-errorunknown' );
204 $this->logError( $status );
205 return $this->error( $status );
210 /** @deprecated class alias since 1.42 */
211 class_alias( PoolCounterWork::class, 'PoolCounterWork' );