MediaWiki REL1_39
ReplicatedBagOStuff.php
Go to the documentation of this file.
1<?php
21use Wikimedia\ObjectFactory\ObjectFactory;
22
36 private $writeStore;
38 private $readStore;
39
41 private $consistencyWindow;
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
89 : $this->readStore;
90
91 return $store->proxyCall(
92 __FUNCTION__,
93 self::ARG0_KEY,
94 self::RES_NONKEY,
95 func_get_args(),
96 $this
97 );
98 }
99
100 public function set( $key, $value, $exptime = 0, $flags = 0 ) {
101 $this->remarkRecentSessionWrite( [ $key ] );
102
103 return $this->writeStore->proxyCall(
104 __FUNCTION__,
105 self::ARG0_KEY,
106 self::RES_NONKEY,
107 func_get_args(),
108 $this
109 );
110 }
111
112 public function delete( $key, $flags = 0 ) {
113 $this->remarkRecentSessionWrite( [ $key ] );
114
115 return $this->writeStore->proxyCall(
116 __FUNCTION__,
117 self::ARG0_KEY,
118 self::RES_NONKEY,
119 func_get_args(),
120 $this
121 );
122 }
123
124 public function add( $key, $value, $exptime = 0, $flags = 0 ) {
125 $this->remarkRecentSessionWrite( [ $key ] );
126
127 return $this->writeStore->proxyCall(
128 __FUNCTION__,
129 self::ARG0_KEY,
130 self::RES_NONKEY,
131 func_get_args(),
132 $this
133 );
134 }
135
136 public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
137 $this->remarkRecentSessionWrite( [ $key ] );
138
139 return $this->writeStore->proxyCall(
140 __FUNCTION__,
141 self::ARG0_KEY,
142 self::RES_NONKEY,
143 func_get_args(),
144 $this
145 );
146 }
147
148 public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
149 $this->remarkRecentSessionWrite( [ $key ] );
150
151 return $this->writeStore->proxyCall(
152 __FUNCTION__,
153 self::ARG0_KEY,
154 self::RES_NONKEY,
155 func_get_args(),
156 $this
157 );
158 }
159
160 public function lock( $key, $timeout = 6, $exptime = 6, $rclass = '' ) {
161 return $this->writeStore->proxyCall(
162 __FUNCTION__,
163 self::ARG0_KEY,
164 self::RES_NONKEY,
165 func_get_args(),
166 $this
167 );
168 }
169
170 public function unlock( $key ) {
171 return $this->writeStore->proxyCall(
172 __FUNCTION__,
173 self::ARG0_KEY,
174 self::RES_NONKEY,
175 func_get_args(),
176 $this
177 );
178 }
179
181 $timestamp,
182 callable $progress = null,
183 $limit = INF,
184 string $tag = null
185 ) {
186 return $this->writeStore->proxyCall(
187 __FUNCTION__,
188 self::ARG0_NONKEY,
189 self::RES_NONKEY,
190 func_get_args(),
191 $this
192 );
193 }
194
195 public function getMulti( array $keys, $flags = 0 ) {
196 $store = (
197 $this->hadRecentSessionWrite( $keys ) ||
198 $this->fieldHasFlags( $flags, self::READ_LATEST )
199 )
200 // Try to maintain session consistency and respect READ_LATEST
201 ? $this->writeStore
202 // Otherwise, just use the default "read" store
203 : $this->readStore;
204
205 return $store->proxyCall(
206 __FUNCTION__,
207 self::ARG0_KEYARR,
208 self::RES_KEYMAP,
209 func_get_args(),
210 $this
211 );
212 }
213
214 public function setMulti( array $valueByKey, $exptime = 0, $flags = 0 ) {
215 $this->remarkRecentSessionWrite( array_keys( $valueByKey ) );
216
217 return $this->writeStore->proxyCall(
218 __FUNCTION__,
219 self::ARG0_KEYMAP,
220 self::RES_KEYMAP,
221 func_get_args(),
222 $this
223 );
224 }
225
226 public function deleteMulti( array $keys, $flags = 0 ) {
227 $this->remarkRecentSessionWrite( $keys );
228
229 return $this->writeStore->proxyCall(
230 __FUNCTION__,
231 self::ARG0_KEYARR,
232 self::RES_NONKEY,
233 func_get_args(),
234 $this
235 );
236 }
237
238 public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
239 $this->remarkRecentSessionWrite( $keys );
240
241 return $this->writeStore->proxyCall(
242 __FUNCTION__,
243 self::ARG0_KEYARR,
244 self::RES_NONKEY,
245 func_get_args(),
246 $this
247 );
248 }
249
250 public function incr( $key, $value = 1, $flags = 0 ) {
251 $this->remarkRecentSessionWrite( [ $key ] );
252
253 return $this->writeStore->proxyCall(
254 __FUNCTION__,
255 self::ARG0_KEY,
256 self::RES_NONKEY,
257 func_get_args(),
258 $this
259 );
260 }
261
262 public function decr( $key, $value = 1, $flags = 0 ) {
263 $this->remarkRecentSessionWrite( [ $key ] );
264
265 return $this->writeStore->proxyCall(
266 __FUNCTION__,
267 self::ARG0_KEY,
268 self::RES_NONKEY,
269 func_get_args(),
270 $this
271 );
272 }
273
274 public function incrWithInit( $key, $exptime, $step = 1, $init = null, $flags = 0 ) {
275 $this->remarkRecentSessionWrite( [ $key ] );
276
277 return $this->writeStore->proxyCall(
278 __FUNCTION__,
279 self::ARG0_KEY,
280 self::RES_NONKEY,
281 func_get_args(),
282 $this
283 );
284 }
285
286 public function makeKeyInternal( $keyspace, $components ) {
287 return $this->genericKeyFromComponents( $keyspace, ...$components );
288 }
289
290 public function makeKey( $collection, ...$components ) {
291 return $this->genericKeyFromComponents( $this->keyspace, $collection, ...$components );
292 }
293
294 public function makeGlobalKey( $collection, ...$components ) {
295 return $this->genericKeyFromComponents( self::GLOBAL_KEYSPACE, $collection, ...$components );
296 }
297
298 protected function convertGenericKey( $key ) {
299 // short-circuit; already uses "generic" keys
300 return $key;
301 }
302
303 public function addBusyCallback( callable $workCallback ) {
304 return $this->writeStore->addBusyCallback( $workCallback );
305 }
306
307 public function setNewPreparedValues( array $valueByKey ) {
308 return $this->writeStore->proxyCall(
309 __FUNCTION__,
310 self::ARG0_KEYMAP,
311 self::RES_NONKEY,
312 func_get_args(),
313 $this
314 );
315 }
316
317 public function setMockTime( &$time ) {
318 parent::setMockTime( $time );
319 $this->writeStore->setMockTime( $time );
320 $this->readStore->setMockTime( $time );
321 }
322
327 private function hadRecentSessionWrite( array $keys ) {
328 $now = $this->getCurrentTime();
329 foreach ( $keys as $key ) {
330 $ts = $this->lastKeyWrites[$key] ?? 0;
331 if ( $ts && ( $now - $ts ) <= $this->consistencyWindow ) {
332 return true;
333 }
334 }
335
336 return false;
337 }
338
342 private function remarkRecentSessionWrite( array $keys ) {
343 $now = $this->getCurrentTime();
344 foreach ( $keys as $key ) {
345 // move to the end
346 unset( $this->lastKeyWrites[$key] );
347 $this->lastKeyWrites[$key] = $now;
348 }
349 // Prune out the map if the first key is obsolete
350 if ( ( $now - reset( $this->lastKeyWrites ) ) > $this->consistencyWindow ) {
351 $this->lastKeyWrites = array_filter(
352 $this->lastKeyWrites,
353 function ( $timestamp ) use ( $now ) {
354 return ( ( $now - $timestamp ) <= $this->consistencyWindow );
355 }
356 );
357 }
358 }
359}
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:85
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.
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 items.
incrWithInit( $key, $exptime, $step=1, $init=null, $flags=0)
Increase the value of the given key (no TTL change) if it exists or create it otherwise.
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.
deleteMulti(array $keys, $flags=0)
Delete a batch of items.
getMulti(array $keys, $flags=0)
Get a batch of items.
changeTTL( $key, $exptime=0, $flags=0)
Change the expiration on an item.
decr( $key, $value=1, $flags=0)
Decrease stored value of $key by $value while preserving its TTL.
setNewPreparedValues(array $valueByKey)
Stage a set of new key values for storage and estimate the amount of bytes needed.
setMulti(array $valueByKey, $exptime=0, $flags=0)
Set a batch of items.
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.
merge( $key, callable $callback, $exptime=0, $attempts=10, $flags=0)
Merge changes into the existing cache value (possibly creating a new one)
unlock( $key)
Release an advisory lock on a key string.
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.