%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/includes/libs/objectcache/ |
| Current File : /www/varak.net/wiki.varak.net/includes/libs/objectcache/WANObjectCacheReaper.php |
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Cache
*/
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Wikimedia\ScopedCallback;
/**
* Class for scanning through chronological, log-structured data or change logs
* and locally purging cache keys related to entities that appear in this data.
*
* This is useful for repairing cache when purges are missed by using a reliable
* stream, such as Kafka or a replicated MySQL table. Purge loss between datacenters
* is expected to be more common than within them.
*
* @since 1.28
*/
class WANObjectCacheReaper implements LoggerAwareInterface {
/** @var WANObjectCache */
protected $cache;
/** @var BagOStuff */
protected $store;
/** @var callable */
protected $logChunkCallback;
/** @var callable */
protected $keyListCallback;
/** @var LoggerInterface */
protected $logger;
/** @var string */
protected $channel;
/** @var int */
protected $initialStartWindow;
/**
* @param WANObjectCache $cache Cache to reap bad keys from
* @param BagOStuff $store Cache to store positions use for locking
* @param callable $logCallback Callback taking arguments:
* - The starting position as a UNIX timestamp
* - The starting unique ID used for breaking timestamp collisions or null
* - The ending position as a UNIX timestamp
* - The maximum number of results to return
* It returns a list of maps of (key: cache key, pos: UNIX timestamp, id: unique ID)
* for each key affected, with the corrosponding event timestamp/ID information.
* The events should be in ascending order, by (timestamp,id).
* @param callable $keyCallback Callback taking arguments:
* - The WANObjectCache instance
* - An object from the event log
* It should return a list of WAN cache keys.
* The callback must fully duck-type test the object, since can be any model class.
* @param array $params Additional options:
* - channel: the name of the update event stream.
* Default: WANObjectCache::DEFAULT_PURGE_CHANNEL.
* - initialStartWindow: seconds back in time to start if the position is lost.
* Default: 1 hour.
* - logger: an SPL monolog instance [optional]
*/
public function __construct(
WANObjectCache $cache,
BagOStuff $store,
callable $logCallback,
callable $keyCallback,
array $params
) {
$this->cache = $cache;
$this->store = $store;
$this->logChunkCallback = $logCallback;
$this->keyListCallback = $keyCallback;
if ( isset( $params['channel'] ) ) {
$this->channel = $params['channel'];
} else {
throw new UnexpectedValueException( "No channel specified." );
}
$this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
$this->logger = $params['logger'] ?? new NullLogger();
}
public function setLogger( LoggerInterface $logger ) {
$this->logger = $logger;
}
/**
* Check and reap stale keys based on a chunk of events
*
* @param int $n Number of events
* @return int Number of keys checked
*/
final public function invoke( $n = 100 ) {
$posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
$scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
if ( !$scopeLock ) {
return 0;
}
$now = time();
$status = $this->store->get( $posKey );
if ( !$status ) {
$status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
}
// Get events for entities who's keys tombstones/hold-off should have expired by now
$events = call_user_func_array(
$this->logChunkCallback,
[ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
);
$event = null;
$keyEvents = [];
foreach ( $events as $event ) {
$keys = call_user_func_array(
$this->keyListCallback,
[ $this->cache, $event['item'] ]
);
foreach ( $keys as $key ) {
unset( $keyEvents[$key] ); // use only the latest per key
$keyEvents[$key] = [
'pos' => $event['pos'],
'id' => $event['id']
];
}
}
$purgeCount = 0;
$lastOkEvent = null;
foreach ( $keyEvents as $key => $keyEvent ) {
if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
break;
}
++$purgeCount;
$lastOkEvent = $event;
}
if ( $lastOkEvent ) {
$ok = $this->store->merge(
$posKey,
function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
if ( !$curValue ) {
// Use new position
} else {
$curCoord = [ $curValue['pos'], $curValue['id'] ];
$newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
if ( $newCoord < $curCoord ) {
// Keep prior position instead of rolling it back
return $curValue;
}
}
return [
'pos' => $lastOkEvent['pos'],
'id' => $lastOkEvent['id'],
'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
];
},
IExpiringStore::TTL_INDEFINITE
);
$pos = $lastOkEvent['pos'];
$id = $lastOkEvent['id'];
if ( $ok ) {
$this->logger->info( "Updated cache reap position ($pos, $id)." );
} else {
$this->logger->error( "Could not update cache reap position ($pos, $id)." );
}
}
ScopedCallback::consume( $scopeLock );
return $purgeCount;
}
/**
* @return array|bool Returns (pos, id) map or false if not set
*/
public function getState() {
$posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
return $this->store->get( $posKey );
}
}