MediaWiki master
ReplicatedBagOStuff.php
Go to the documentation of this file.
1<?php
21use Wikimedia\ObjectFactory\ObjectFactory;
22
37 private $writeStore;
39 private $readStore;
40
42 private $consistencyWindow;
44 private $lastKeyWrites = [];
45
47 private const MAX_WRITE_DELAY = 5;
48
62 public function __construct( $params ) {
63 parent::__construct( $params );
64
65 if ( !isset( $params['writeFactory'] ) ) {
66 throw new InvalidArgumentException(
67 __METHOD__ . ': the "writeFactory" parameter is required' );
68 } elseif ( !isset( $params['readFactory'] ) ) {
69 throw new InvalidArgumentException(
70 __METHOD__ . ': the "readFactory" parameter is required' );
71 }
72
73 $this->consistencyWindow = $params['sessionConsistencyWindow'] ?? self::MAX_WRITE_DELAY;
74 $this->writeStore = ( $params['writeFactory'] instanceof BagOStuff )
75 ? $params['writeFactory']
76 : ObjectFactory::getObjectFromSpec( $params['writeFactory'] );
77 $this->readStore = ( $params['readFactory'] instanceof BagOStuff )
78 ? $params['readFactory']
79 : ObjectFactory::getObjectFromSpec( $params['readFactory'] );
80 $this->attrMap = $this->mergeFlagMaps( [ $this->readStore, $this->writeStore ] );
81 }
82
83 public function get( $key, $flags = 0 ) {
84 $store = (
85 $this->hadRecentSessionWrite( [ $key ] ) ||
86 $this->fieldHasFlags( $flags, self::READ_LATEST )
87 )
88 // Try to maintain session consistency and respect READ_LATEST
89 ? $this->writeStore
90 // Otherwise, just use the default "read" store
91 : $this->readStore;
92
93 return $store->proxyCall(
94 __FUNCTION__,
95 self::ARG0_KEY,
96 self::RES_NONKEY,
97 func_get_args(),
98 $this
99 );
100 }
101
102 public function set( $key, $value, $exptime = 0, $flags = 0 ) {
103 $this->remarkRecentSessionWrite( [ $key ] );
104
105 return $this->writeStore->proxyCall(
106 __FUNCTION__,
107 self::ARG0_KEY,
108 self::RES_NONKEY,
109 func_get_args(),
110 $this
111 );
112 }
113
114 public function delete( $key, $flags = 0 ) {
115 $this->remarkRecentSessionWrite( [ $key ] );
116
117 return $this->writeStore->proxyCall(
118 __FUNCTION__,
119 self::ARG0_KEY,
120 self::RES_NONKEY,
121 func_get_args(),
122 $this
123 );
124 }
125
126 public function add( $key, $value, $exptime = 0, $flags = 0 ) {
127 $this->remarkRecentSessionWrite( [ $key ] );
128
129 return $this->writeStore->proxyCall(
130 __FUNCTION__,
131 self::ARG0_KEY,
132 self::RES_NONKEY,
133 func_get_args(),
134 $this
135 );
136 }
137
138 public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $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 $this
147 );
148 }
149
150 public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
151 $this->remarkRecentSessionWrite( [ $key ] );
152
153 return $this->writeStore->proxyCall(
154 __FUNCTION__,
155 self::ARG0_KEY,
156 self::RES_NONKEY,
157 func_get_args(),
158 $this
159 );
160 }
161
162 public function lock( $key, $timeout = 6, $exptime = 6, $rclass = '' ) {
163 return $this->writeStore->proxyCall(
164 __FUNCTION__,
165 self::ARG0_KEY,
166 self::RES_NONKEY,
167 func_get_args(),
168 $this
169 );
170 }
171
172 public function unlock( $key ) {
173 return $this->writeStore->proxyCall(
174 __FUNCTION__,
175 self::ARG0_KEY,
176 self::RES_NONKEY,
177 func_get_args(),
178 $this
179 );
180 }
181
183 $timestamp,
184 callable $progress = null,
185 $limit = INF,
186 string $tag = null
187 ) {
188 return $this->writeStore->proxyCall(
189 __FUNCTION__,
190 self::ARG0_NONKEY,
191 self::RES_NONKEY,
192 func_get_args(),
193 $this
194 );
195 }
196
197 public function getMulti( array $keys, $flags = 0 ) {
198 $store = (
199 $this->hadRecentSessionWrite( $keys ) ||
200 $this->fieldHasFlags( $flags, self::READ_LATEST )
201 )
202 // Try to maintain session consistency and respect READ_LATEST
203 ? $this->writeStore
204 // Otherwise, just use the default "read" store
205 : $this->readStore;
206
207 return $store->proxyCall(
208 __FUNCTION__,
209 self::ARG0_KEYARR,
210 self::RES_KEYMAP,
211 func_get_args(),
212 $this
213 );
214 }
215
216 public function setMulti( array $valueByKey, $exptime = 0, $flags = 0 ) {
217 $this->remarkRecentSessionWrite( array_keys( $valueByKey ) );
218
219 return $this->writeStore->proxyCall(
220 __FUNCTION__,
221 self::ARG0_KEYMAP,
222 self::RES_NONKEY,
223 func_get_args(),
224 $this
225 );
226 }
227
228 public function deleteMulti( array $keys, $flags = 0 ) {
229 $this->remarkRecentSessionWrite( $keys );
230
231 return $this->writeStore->proxyCall(
232 __FUNCTION__,
233 self::ARG0_KEYARR,
234 self::RES_NONKEY,
235 func_get_args(),
236 $this
237 );
238 }
239
240 public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
241 $this->remarkRecentSessionWrite( $keys );
242
243 return $this->writeStore->proxyCall(
244 __FUNCTION__,
245 self::ARG0_KEYARR,
246 self::RES_NONKEY,
247 func_get_args(),
248 $this
249 );
250 }
251
252 public function incrWithInit( $key, $exptime, $step = 1, $init = null, $flags = 0 ) {
253 $this->remarkRecentSessionWrite( [ $key ] );
254
255 return $this->writeStore->proxyCall(
256 __FUNCTION__,
257 self::ARG0_KEY,
258 self::RES_NONKEY,
259 func_get_args(),
260 $this
261 );
262 }
263
264 public function setMockTime( &$time ) {
265 parent::setMockTime( $time );
266 $this->writeStore->setMockTime( $time );
267 $this->readStore->setMockTime( $time );
268 }
269
274 private function hadRecentSessionWrite( array $keys ) {
275 $now = $this->getCurrentTime();
276 foreach ( $keys as $key ) {
277 $ts = $this->lastKeyWrites[$key] ?? 0;
278 if ( $ts && ( $now - $ts ) <= $this->consistencyWindow ) {
279 return true;
280 }
281 }
282
283 return false;
284 }
285
289 private function remarkRecentSessionWrite( array $keys ) {
290 $now = $this->getCurrentTime();
291 foreach ( $keys as $key ) {
292 // move to the end
293 unset( $this->lastKeyWrites[$key] );
294 $this->lastKeyWrites[$key] = $now;
295 }
296 // Prune out the map if the first key is obsolete
297 if ( ( $now - reset( $this->lastKeyWrites ) ) > $this->consistencyWindow ) {
298 $this->lastKeyWrites = array_filter(
299 $this->lastKeyWrites,
300 function ( $timestamp ) use ( $now ) {
301 return ( ( $now - $timestamp ) <= $this->consistencyWindow );
302 }
303 );
304 }
305 }
306}
array $params
The job parameters.
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.
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.
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.
setMulti(array $valueByKey, $exptime=0, $flags=0)
Set a batch of items.
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.