MediaWiki REL1_37
ReplicatedBagOStuff.php
Go to the documentation of this file.
1<?php
21use Wikimedia\ObjectFactory;
22
36 private $writeStore;
38 private $readStore;
39
43 private $lastKeyWrites = [];
44
46 private const MAX_WRITE_DELAY = 5;
47
60 public function __construct( $params ) {
61 parent::__construct( $params );
62
63 if ( !isset( $params['writeFactory'] ) ) {
64 throw new InvalidArgumentException(
65 __METHOD__ . ': the "writeFactory" parameter is required' );
66 } elseif ( !isset( $params['readFactory'] ) ) {
67 throw new InvalidArgumentException(
68 __METHOD__ . ': the "readFactory" parameter is required' );
69 }
70
71 $this->consistencyWindow = $params['sessionConsistencyWindow'] ?? self::MAX_WRITE_DELAY;
72 $this->writeStore = ( $params['writeFactory'] instanceof BagOStuff )
73 ? $params['writeFactory']
74 : ObjectFactory::getObjectFromSpec( $params['writeFactory'] );
75 $this->readStore = ( $params['readFactory'] instanceof BagOStuff )
76 ? $params['readFactory']
77 : ObjectFactory::getObjectFromSpec( $params['readFactory'] );
78 $this->attrMap = $this->mergeFlagMaps( [ $this->readStore, $this->writeStore ] );
79 }
80
81 public function get( $key, $flags = 0 ) {
82 $store = (
83 $this->hadRecentSessionWrite( [ $key ] ) ||
84 $this->fieldHasFlags( $flags, self::READ_LATEST )
85 )
86 // Try to maintain session consistency and respect READ_LATEST
87 ? $this->writeStore
88 // Otherwise, just use the default "read" store
90
91 return $store->proxyCall( __FUNCTION__, self::ARG0_KEY, self::RES_NONKEY, func_get_args() );
92 }
93
94 public function set( $key, $value, $exptime = 0, $flags = 0 ) {
95 $this->remarkRecentSessionWrite( [ $key ] );
96
97 return $this->writeStore->proxyCall(
98 __FUNCTION__,
99 self::ARG0_KEY,
100 self::RES_NONKEY,
101 func_get_args()
102 );
103 }
104
105 public function delete( $key, $flags = 0 ) {
106 $this->remarkRecentSessionWrite( [ $key ] );
107
108 return $this->writeStore->proxyCall(
109 __FUNCTION__,
110 self::ARG0_KEY,
111 self::RES_NONKEY,
112 func_get_args()
113 );
114 }
115
116 public function add( $key, $value, $exptime = 0, $flags = 0 ) {
117 $this->remarkRecentSessionWrite( [ $key ] );
118
119 return $this->writeStore->proxyCall(
120 __FUNCTION__,
121 self::ARG0_KEY,
122 self::RES_NONKEY,
123 func_get_args()
124 );
125 }
126
127 public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
128 $this->remarkRecentSessionWrite( [ $key ] );
129
130 return $this->writeStore->proxyCall(
131 __FUNCTION__,
132 self::ARG0_KEY,
133 self::RES_NONKEY,
134 func_get_args()
135 );
136 }
137
138 public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
139 $this->remarkRecentSessionWrite( [ $key ] );
140
141 return $this->writeStore->proxyCall(
142 __FUNCTION__,
143 self::ARG0_KEY,
144 self::RES_NONKEY,
145 func_get_args()
146 );
147 }
148
149 public function lock( $key, $timeout = 6, $exptime = 6, $rclass = '' ) {
150 return $this->writeStore->proxyCall(
151 __FUNCTION__,
152 self::ARG0_KEY,
153 self::RES_NONKEY,
154 func_get_args()
155 );
156 }
157
158 public function unlock( $key ) {
159 return $this->writeStore->proxyCall(
160 __FUNCTION__,
161 self::ARG0_KEY,
162 self::RES_NONKEY,
163 func_get_args()
164 );
165 }
166
168 $timestamp,
169 callable $progress = null,
170 $limit = INF,
171 string $tag = null
172 ) {
173 return $this->writeStore->proxyCall(
174 __FUNCTION__,
175 self::ARG0_NONKEY,
176 self::RES_NONKEY,
177 func_get_args()
178 );
179 }
180
181 public function getMulti( array $keys, $flags = 0 ) {
182 $store = (
183 $this->hadRecentSessionWrite( $keys ) ||
184 $this->fieldHasFlags( $flags, self::READ_LATEST )
185 )
186 // Try to maintain session consistency and respect READ_LATEST
187 ? $this->writeStore
188 // Otherwise, just use the default "read" store
190
191 return $store->proxyCall( __FUNCTION__, self::ARG0_KEYARR, self::RES_KEYMAP, func_get_args() );
192 }
193
194 public function setMulti( array $valueByKey, $exptime = 0, $flags = 0 ) {
195 $this->remarkRecentSessionWrite( array_keys( $valueByKey ) );
196
197 return $this->writeStore->proxyCall(
198 __FUNCTION__,
199 self::ARG0_KEYMAP,
200 self::RES_NONKEY,
201 func_get_args()
202 );
203 }
204
205 public function deleteMulti( array $keys, $flags = 0 ) {
206 $this->remarkRecentSessionWrite( $keys );
207
208 return $this->writeStore->proxyCall(
209 __FUNCTION__,
210 self::ARG0_KEYARR,
211 self::RES_NONKEY,
212 func_get_args()
213 );
214 }
215
216 public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
217 $this->remarkRecentSessionWrite( $keys );
218
219 return $this->writeStore->proxyCall(
220 __FUNCTION__,
221 self::ARG0_KEYARR,
222 self::RES_NONKEY,
223 func_get_args()
224 );
225 }
226
227 public function incr( $key, $value = 1, $flags = 0 ) {
228 $this->remarkRecentSessionWrite( [ $key ] );
229
230 return $this->writeStore->proxyCall(
231 __FUNCTION__,
232 self::ARG0_KEY,
233 self::RES_NONKEY,
234 func_get_args()
235 );
236 }
237
238 public function decr( $key, $value = 1, $flags = 0 ) {
239 $this->remarkRecentSessionWrite( [ $key ] );
240
241 return $this->writeStore->proxyCall(
242 __FUNCTION__,
243 self::ARG0_KEY,
244 self::RES_NONKEY,
245 func_get_args()
246 );
247 }
248
249 public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 ) {
250 $this->remarkRecentSessionWrite( [ $key ] );
251
252 return $this->writeStore->proxyCall(
253 __FUNCTION__,
254 self::ARG0_KEY,
255 self::RES_NONKEY,
256 func_get_args()
257 );
258 }
259
260 public function getLastError() {
261 return ( $this->writeStore->getLastError() !== self::ERR_NONE )
262 ? $this->writeStore->getLastError()
263 : $this->readStore->getLastError();
264 }
265
266 public function clearLastError() {
267 $this->writeStore->clearLastError();
268 $this->readStore->clearLastError();
269 }
270
271 public function makeKeyInternal( $keyspace, $components ) {
272 return $this->genericKeyFromComponents( $keyspace, ...$components );
273 }
274
275 public function makeKey( $collection, ...$components ) {
276 return $this->genericKeyFromComponents( $this->keyspace, $collection, ...$components );
277 }
278
279 public function makeGlobalKey( $collection, ...$components ) {
280 return $this->genericKeyFromComponents( self::GLOBAL_KEYSPACE, $collection, ...$components );
281 }
282
283 protected function convertGenericKey( $key ) {
284 return $key; // short-circuit; already uses "generic" keys
285 }
286
287 public function addBusyCallback( callable $workCallback ) {
288 return $this->writeStore->addBusyCallback( $workCallback );
289 }
290
291 public function setNewPreparedValues( array $valueByKey ) {
292 return $this->writeStore->proxyCall(
293 __FUNCTION__,
294 self::ARG0_KEYMAP,
295 self::RES_NONKEY,
296 func_get_args()
297 );
298 }
299
300 public function setMockTime( &$time ) {
301 parent::setMockTime( $time );
302 $this->writeStore->setMockTime( $time );
303 $this->readStore->setMockTime( $time );
304 }
305
310 private function hadRecentSessionWrite( array $keys ) {
311 $now = $this->getCurrentTime();
312 foreach ( $keys as $key ) {
313 $ts = $this->lastKeyWrites[$key] ?? 0;
314 if ( $ts && ( $now - $ts ) <= $this->consistencyWindow ) {
315 return true;
316 }
317 }
318
319 return false;
320 }
321
325 private function remarkRecentSessionWrite( array $keys ) {
326 $now = $this->getCurrentTime();
327 foreach ( $keys as $key ) {
328 unset( $this->lastKeyWrites[$key] ); // move to the end
329 $this->lastKeyWrites[$key] = $now;
330 }
331 // Prune out the map if the first key is obsolete
332 if ( ( $now - reset( $this->lastKeyWrites ) ) > $this->consistencyWindow ) {
333 $this->lastKeyWrites = array_filter(
334 $this->lastKeyWrites,
335 function ( $timestamp ) use ( $now ) {
336 return ( ( $now - $timestamp ) <= $this->consistencyWindow );
337 }
338 );
339 }
340 }
341}
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:86
mergeFlagMaps(array $bags)
Merge the flag maps of one or more BagOStuff objects into a "lowest common denominator" map.
genericKeyFromComponents(... $components)
At a minimum, there must be a keyspace and collection name component.
proxyCall( $method, $arg0Sig, $resSig, array $genericArgs)
Call a method on behalf of wrapper BagOStuff instance that uses "generic" keys.
string $keyspace
Default keyspace; used by makeKey()
fieldHasFlags( $field, $flags)
A cache class that directs writes to one set of servers and reads to another.
add( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
deleteObjectsExpiringBefore( $timestamp, callable $progress=null, $limit=INF, string $tag=null)
Delete all objects expiring before a certain date.
__construct( $params)
Constructor.
changeTTLMulti(array $keys, $exptime, $flags=0)
Change the expiration of multiple keys that exist.
clearLastError()
Clear the "last error" registry.
lock( $key, $timeout=6, $exptime=6, $rclass='')
Acquire an advisory lock on a key string, exclusive to the caller.
incr( $key, $value=1, $flags=0)
Increase stored value of $key by $value while preserving its TTL.
makeGlobalKey( $collection,... $components)
Make a cache key for the default keyspace and given components.
convertGenericKey( $key)
Convert a "generic" reversible cache key into one for this cache.
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
deleteMulti(array $keys, $flags=0)
Batch deletion.
remarkRecentSessionWrite(array $keys)
getMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
changeTTL( $key, $exptime=0, $flags=0)
Change the expiration on a key if it exists.
decr( $key, $value=1, $flags=0)
Decrease stored value of $key by $value while preserving its TTL.
setNewPreparedValues(array $valueByKey)
Make a "generic" reversible cache key from the given components.
setMulti(array $valueByKey, $exptime=0, $flags=0)
Batch insertion/replace.
addBusyCallback(callable $workCallback)
Let a callback be run to avoid wasting time on special blocking calls.
makeKeyInternal( $keyspace, $components)
Make a cache key for the given keyspace and components.
int $consistencyWindow
Seconds to read from the master source for a key after writing to it.
merge( $key, callable $callback, $exptime=0, $attempts=10, $flags=0)
Merge changes into the existing cache value (possibly creating a new one)
float[] $lastKeyWrites
Map of (key => UNIX timestamp)
unlock( $key)
Release an advisory lock on a key string.
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
incrWithInit( $key, $exptime, $value=1, $init=null, $flags=0)
Increase the value of the given key (no TTL change) if it exists or create it otherwise.