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