MediaWiki  master
BagOStuff.php
Go to the documentation of this file.
1 <?php
29 use Psr\Log\LoggerAwareInterface;
30 use Psr\Log\LoggerInterface;
31 use Psr\Log\NullLogger;
34 use Wikimedia\ScopedCallback;
35 
66 abstract class BagOStuff implements
70  LoggerAwareInterface
71 {
73  protected $logger;
75  protected $asyncHandler;
76 
78  protected $attrMap = [];
79 
81  protected $keyspace;
82 
84  protected $debugMode = false;
85 
88 
90  public const READ_LATEST = 1; // if supported, avoid reading stale data due to replication
91  public const READ_VERIFIED = 2; // promise that the caller handles detection of staleness
93  public const WRITE_SYNC = 4; // if supported, block until the write is fully replicated
94  public const WRITE_CACHE_ONLY = 8; // only change state of the in-memory cache
95  public const WRITE_ALLOW_SEGMENTS = 16; // allow partitioning of the value if it is large
96  public const WRITE_PRUNE_SEGMENTS = 32; // delete all the segments if the value is partitioned
97  public const WRITE_BACKGROUND = 64; // if supported, do not block on completion until the next read
98 
100  protected const GLOBAL_KEYSPACE = 'global';
102  protected const GLOBAL_PREFIX = 'global:';
104  protected const GLOBAL_PREFIX_LEN = 7;
105 
107  protected const ARG0_KEY = 0;
109  protected const ARG0_KEYARR = 1;
111  protected const ARG0_KEYMAP = 2;
113  protected const ARG0_NONKEY = 3;
114 
116  protected const RES_KEYMAP = 0;
118  protected const RES_NONKEY = 1;
119 
131  public function __construct( array $params = [] ) {
132  $this->keyspace = $params['keyspace'] ?? 'local';
133  $this->setLogger( $params['logger'] ?? new NullLogger() );
134  $this->asyncHandler = $params['asyncHandler'] ?? null;
135  }
136 
141  public function setLogger( LoggerInterface $logger ) {
142  $this->logger = $logger;
143  }
144 
149  public function getLogger() : LoggerInterface {
150  return $this->logger;
151  }
152 
156  public function setDebug( $enabled ) {
157  $this->debugMode = $enabled;
158  }
159 
173  final public function getWithSetCallback( $key, $exptime, $callback, $flags = 0 ) {
174  $value = $this->get( $key, $flags );
175 
176  if ( $value === false ) {
177  $value = $callback( $exptime );
178  if ( $value !== false && $exptime >= 0 ) {
179  $this->set( $key, $value, $exptime, $flags );
180  }
181  }
182 
183  return $value;
184  }
185 
199  abstract public function get( $key, $flags = 0 );
200 
210  abstract public function set( $key, $value, $exptime = 0, $flags = 0 );
211 
223  abstract public function delete( $key, $flags = 0 );
224 
234  abstract public function add( $key, $value, $exptime = 0, $flags = 0 );
235 
253  abstract public function merge(
254  $key,
255  callable $callback,
256  $exptime = 0,
257  $attempts = 10,
258  $flags = 0
259  );
260 
278  abstract public function changeTTL( $key, $exptime = 0, $flags = 0 );
279 
291  abstract public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' );
292 
299  abstract public function unlock( $key );
300 
317  final public function getScopedLock( $key, $timeout = 6, $expiry = 30, $rclass = '' ) {
318  $expiry = min( $expiry ?: INF, self::TTL_DAY );
319 
320  if ( !$this->lock( $key, $timeout, $expiry, $rclass ) ) {
321  return null;
322  }
323 
324  $lSince = $this->getCurrentTime(); // lock timestamp
325 
326  return new ScopedCallback( function () use ( $key, $lSince, $expiry ) {
327  $latency = 0.050; // latency skew (err towards keeping lock present)
328  $age = ( $this->getCurrentTime() - $lSince + $latency );
329  if ( ( $age + $latency ) >= $expiry ) {
330  $this->logger->warning(
331  "Lock for {key} held too long ({age} sec).",
332  [ 'key' => $key, 'age' => $age ]
333  );
334  return; // expired; it's not "safe" to delete the key
335  }
336  $this->unlock( $key );
337  } );
338  }
339 
350  abstract public function deleteObjectsExpiringBefore(
351  $timestamp,
352  callable $progress = null,
353  $limit = INF
354  );
355 
363  abstract public function getMulti( array $keys, $flags = 0 );
364 
378  abstract public function setMulti( array $valueByKey, $exptime = 0, $flags = 0 );
379 
392  abstract public function deleteMulti( array $keys, $flags = 0 );
393 
405  abstract public function changeTTLMulti( array $keys, $exptime, $flags = 0 );
406 
415  abstract public function incr( $key, $value = 1, $flags = 0 );
416 
425  abstract public function decr( $key, $value = 1, $flags = 0 );
426 
442  abstract public function incrWithInit( $key, $exptime, $value = 1, $init = null, $flags = 0 );
443 
449  abstract public function getLastError();
450 
455  abstract public function clearLastError();
456 
477  abstract public function addBusyCallback( callable $workCallback );
478 
492  abstract public function makeKeyInternal( $keyspace, $components );
493 
502  abstract public function makeGlobalKey( $class, ...$components );
503 
512  abstract public function makeKey( $class, ...$components );
513 
521  public function isKeyGlobal( $key ) {
522  return ( strncmp( $key, self::GLOBAL_PREFIX, self::GLOBAL_PREFIX_LEN ) === 0 );
523  }
524 
530  public function getQoS( $flag ) {
531  return $this->attrMap[$flag] ?? self::QOS_UNKNOWN;
532  }
533 
538  public function getSegmentationSize() {
539  return INF;
540  }
541 
546  public function getSegmentedValueMaxSize() {
547  return INF;
548  }
549 
556  final protected function fieldHasFlags( $field, $flags ) {
557  return ( ( $field & $flags ) === $flags );
558  }
559 
566  final protected function mergeFlagMaps( array $bags ) {
567  $map = [];
568  foreach ( $bags as $bag ) {
569  foreach ( $bag->attrMap as $attr => $rank ) {
570  if ( isset( $map[$attr] ) ) {
571  $map[$attr] = min( $map[$attr], $rank );
572  } else {
573  $map[$attr] = $rank;
574  }
575  }
576  }
577 
578  return $map;
579  }
580 
606  abstract public function setNewPreparedValues( array $valueByKey );
607 
615  final protected function genericKeyFromComponents( ...$components ) {
616  if ( count( $components ) < 2 ) {
617  throw new InvalidArgumentException( "Missing keyspace or collection name" );
618  }
619 
620  $key = '';
621  foreach ( $components as $component ) {
622  if ( $key !== '' ) {
623  $key .= ':';
624  }
625  // Escape delimiter (":") and escape ("%") characters
626  $key .= strtr( $component, [ '%' => '%25', ':' => '%3A' ] );
627  }
628 
629  return $key;
630  }
631 
641  final protected function componentsFromGenericKey( $key ) {
642  // Note that the order of each corresponding search/replace pair matters
643  return str_replace( [ '%3A', '%25' ], [ ':', '%' ], explode( ':', $key ) );
644  }
645 
654  abstract protected function convertGenericKey( $key );
655 
665  protected function proxyCall( $method, $arg0Sig, $resSig, array $genericArgs ) {
666  // Get the corresponding store-specific cache keys...
667  $storeArgs = $genericArgs;
668  switch ( $arg0Sig ) {
669  case self::ARG0_KEY:
670  $storeArgs[0] = $this->convertGenericKey( $genericArgs[0] );
671  break;
672  case self::ARG0_KEYARR:
673  foreach ( $genericArgs[0] as $i => $genericKey ) {
674  $storeArgs[0][$i] = $this->convertGenericKey( $genericKey );
675  }
676  break;
677  case self::ARG0_KEYMAP:
678  $storeArgs[0] = [];
679  foreach ( $genericArgs[0] as $genericKey => $v ) {
680  $storeArgs[0][$this->convertGenericKey( $genericKey )] = $v;
681  }
682  break;
683  }
684 
685  // Result of invoking the method with the corresponding store-specific cache keys
686  $storeRes = $this->$method( ...$storeArgs );
687 
688  // Convert any store-specific cache keys in the result back to generic cache keys
689  if ( $resSig === self::RES_KEYMAP ) {
690  // Map of (store-specific cache key => generic cache key)
691  $genericKeyByStoreKey = array_combine( $storeArgs[0], $genericArgs[0] );
692 
693  $genericRes = [];
694  foreach ( $storeRes as $storeKey => $value ) {
695  $genericRes[$genericKeyByStoreKey[$storeKey]] = $value;
696  }
697  } else {
698  $genericRes = $storeRes;
699  }
700 
701  return $genericRes;
702  }
703 
709  public function getCurrentTime() {
710  return $this->wallClockOverride ?: microtime( true );
711  }
712 
718  public function setMockTime( &$time ) {
719  $this->wallClockOverride =& $time;
720  }
721 }
BagOStuff\getSegmentedValueMaxSize
getSegmentedValueMaxSize()
Definition: BagOStuff.php:546
BagOStuff\setNewPreparedValues
setNewPreparedValues(array $valueByKey)
Make a "generic" reversible cache key from the given components.
BagOStuff\getQoS
getQoS( $flag)
Definition: BagOStuff.php:530
BagOStuff\getLastError
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
BagOStuff\add
add( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
BagOStuff\WRITE_SYNC
const WRITE_SYNC
Bitfield constants for set()/merge(); these are only advisory.
Definition: BagOStuff.php:93
BagOStuff\WRITE_ALLOW_SEGMENTS
const WRITE_ALLOW_SEGMENTS
Definition: BagOStuff.php:95
Wikimedia\LightweightObjectStore\ExpirationAwareness
Generic interface providing Time-To-Live constants for expirable object storage.
Definition: ExpirationAwareness.php:32
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:71
BagOStuff\genericKeyFromComponents
genericKeyFromComponents(... $components)
At a minimum, there must be a keyspace and collection name component.
Definition: BagOStuff.php:615
BagOStuff\changeTTL
changeTTL( $key, $exptime=0, $flags=0)
Change the expiration on a key if it exists.
BagOStuff\componentsFromGenericKey
componentsFromGenericKey( $key)
Extract the components from a "generic" reversible cache key.
Definition: BagOStuff.php:641
BagOStuff\convertGenericKey
convertGenericKey( $key)
Convert a "generic" reversible cache key into one for this cache.
BagOStuff\$logger
LoggerInterface $logger
Definition: BagOStuff.php:73
BagOStuff\makeGlobalKey
makeGlobalKey( $class,... $components)
Make a cache key for the default keyspace and given components.
BagOStuff\changeTTLMulti
changeTTLMulti(array $keys, $exptime, $flags=0)
Change the expiration of multiple keys that exist.
BagOStuff\setMockTime
setMockTime(&$time)
Definition: BagOStuff.php:718
BagOStuff\isKeyGlobal
isKeyGlobal( $key)
Check whether a cache key is in the global keyspace.
Definition: BagOStuff.php:521
BagOStuff\unlock
unlock( $key)
Release an advisory lock on a key string.
BagOStuff\clearLastError
clearLastError()
Clear the "last error" registry.
BagOStuff\decr
decr( $key, $value=1, $flags=0)
Decrease stored value of $key by $value while preserving its TTL.
BagOStuff\incr
incr( $key, $value=1, $flags=0)
Increase stored value of $key by $value while preserving its TTL.
BagOStuff\setLogger
setLogger(LoggerInterface $logger)
Definition: BagOStuff.php:141
BagOStuff\$asyncHandler
callable null $asyncHandler
Definition: BagOStuff.php:75
BagOStuff\proxyCall
proxyCall( $method, $arg0Sig, $resSig, array $genericArgs)
Call a method on behalf of wrapper BagOStuff instance that uses "generic" keys.
Definition: BagOStuff.php:665
BagOStuff\WRITE_BACKGROUND
const WRITE_BACKGROUND
Definition: BagOStuff.php:97
BagOStuff\READ_LATEST
const READ_LATEST
Bitfield constants for get()/getMulti(); these are only advisory.
Definition: BagOStuff.php:90
BagOStuff\addBusyCallback
addBusyCallback(callable $workCallback)
Let a callback be run to avoid wasting time on special blocking calls.
BagOStuff\$wallClockOverride
float null $wallClockOverride
Definition: BagOStuff.php:87
BagOStuff\WRITE_PRUNE_SEGMENTS
const WRITE_PRUNE_SEGMENTS
Definition: BagOStuff.php:96
Wikimedia\LightweightObjectStore\StorageAwareness
Generic interface providing error code and quality-of-service constants for object stores.
Definition: StorageAwareness.php:32
BagOStuff\getWithSetCallback
getWithSetCallback( $key, $exptime, $callback, $flags=0)
Get an item with the given key, regenerating and setting it if not found.
Definition: BagOStuff.php:173
BagOStuff\mergeFlagMaps
mergeFlagMaps(array $bags)
Merge the flag maps of one or more BagOStuff objects into a "lowest common denominator" map.
Definition: BagOStuff.php:566
BagOStuff\__construct
__construct(array $params=[])
Parameters include:
Definition: BagOStuff.php:131
BagOStuff\getLogger
getLogger()
Definition: BagOStuff.php:149
BagOStuff\merge
merge( $key, callable $callback, $exptime=0, $attempts=10, $flags=0)
Merge changes into the existing cache value (possibly creating a new one)
BagOStuff\deleteMulti
deleteMulti(array $keys, $flags=0)
Batch deletion.
BagOStuff\makeKey
makeKey( $class,... $components)
Make a cache key for the global keyspace and given components.
BagOStuff\incrWithInit
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.
BagOStuff\makeKeyInternal
makeKeyInternal( $keyspace, $components)
Make a cache key for the given keyspace and components.
BagOStuff\setDebug
setDebug( $enabled)
Definition: BagOStuff.php:156
BagOStuff\setMulti
setMulti(array $valueByKey, $exptime=0, $flags=0)
Batch insertion/replace.
BagOStuff\$debugMode
bool $debugMode
Whether to send debug log entries to the SPI logger instance.
Definition: BagOStuff.php:84
BagOStuff\$attrMap
int[] $attrMap
Map of (ATTR_* class constant => QOS_* class constant)
Definition: BagOStuff.php:78
BagOStuff\READ_VERIFIED
const READ_VERIFIED
Definition: BagOStuff.php:91
BagOStuff\fieldHasFlags
fieldHasFlags( $field, $flags)
Definition: BagOStuff.php:556
BagOStuff\lock
lock( $key, $timeout=6, $expiry=6, $rclass='')
Acquire an advisory lock on a key string.
$keys
$keys
Definition: testCompression.php:72
BagOStuff\getCurrentTime
getCurrentTime()
Definition: BagOStuff.php:709
BagOStuff\$keyspace
string $keyspace
Default keyspace; used by makeKey()
Definition: BagOStuff.php:81
BagOStuff\deleteObjectsExpiringBefore
deleteObjectsExpiringBefore( $timestamp, callable $progress=null, $limit=INF)
Delete all objects expiring before a certain date.
IStoreKeyEncoder
Generic interface for object stores with key encoding methods.
Definition: IStoreKeyEncoder.php:9
BagOStuff\getScopedLock
getScopedLock( $key, $timeout=6, $expiry=30, $rclass='')
Get a lightweight exclusive self-unlocking lock.
Definition: BagOStuff.php:317
BagOStuff\getSegmentationSize
getSegmentationSize()
Definition: BagOStuff.php:538
BagOStuff\getMulti
getMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
BagOStuff\WRITE_CACHE_ONLY
const WRITE_CACHE_ONLY
Definition: BagOStuff.php:94