MediaWiki  master
WANObjectCacheReaper.php
Go to the documentation of this file.
1 <?php
26 
37 class WANObjectCacheReaper implements LoggerAwareInterface {
39  protected $cache;
41  protected $store;
43  protected $logChunkCallback;
45  protected $keyListCallback;
47  protected $logger;
48 
50  protected $channel;
53 
76  public function __construct(
79  callable $logCallback,
80  callable $keyCallback,
81  array $params
82  ) {
83  $this->cache = $cache;
84  $this->store = $store;
85 
86  $this->logChunkCallback = $logCallback;
87  $this->keyListCallback = $keyCallback;
88  if ( isset( $params['channel'] ) ) {
89  $this->channel = $params['channel'];
90  } else {
91  throw new UnexpectedValueException( "No channel specified." );
92  }
93 
94  $this->initialStartWindow = $params['initialStartWindow'] ?? 3600;
95  $this->logger = $params['logger'] ?? new NullLogger();
96  }
97 
98  public function setLogger( LoggerInterface $logger ) {
99  $this->logger = $logger;
100  }
101 
108  final public function invoke( $n = 100 ) {
109  $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
110  $scopeLock = $this->store->getScopedLock( "$posKey:busy", 0 );
111  if ( !$scopeLock ) {
112  return 0;
113  }
114 
115  $now = time();
116  $status = $this->store->get( $posKey );
117  if ( !$status ) {
118  $status = [ 'pos' => $now - $this->initialStartWindow, 'id' => null ];
119  }
120 
121  // Get events for entities who's keys tombstones/hold-off should have expired by now
122  $events = call_user_func_array(
123  $this->logChunkCallback,
124  [ $status['pos'], $status['id'], $now - WANObjectCache::HOLDOFF_TTL - 1, $n ]
125  );
126 
127  $event = null;
128  $keyEvents = [];
129  foreach ( $events as $event ) {
130  $keys = call_user_func_array(
131  $this->keyListCallback,
132  [ $this->cache, $event['item'] ]
133  );
134  foreach ( $keys as $key ) {
135  unset( $keyEvents[$key] ); // use only the latest per key
136  $keyEvents[$key] = [
137  'pos' => $event['pos'],
138  'id' => $event['id']
139  ];
140  }
141  }
142 
143  $purgeCount = 0;
144  $lastOkEvent = null;
145  foreach ( $keyEvents as $key => $keyEvent ) {
146  if ( !$this->cache->reap( $key, $keyEvent['pos'] ) ) {
147  break;
148  }
149  ++$purgeCount;
150  $lastOkEvent = $event;
151  }
152 
153  if ( $lastOkEvent ) {
154  $ok = $this->store->merge(
155  $posKey,
156  function ( $bag, $key, $curValue ) use ( $lastOkEvent ) {
157  if ( !$curValue ) {
158  // Use new position
159  } else {
160  $curCoord = [ $curValue['pos'], $curValue['id'] ];
161  $newCoord = [ $lastOkEvent['pos'], $lastOkEvent['id'] ];
162  if ( $newCoord < $curCoord ) {
163  // Keep prior position instead of rolling it back
164  return $curValue;
165  }
166  }
167 
168  return [
169  'pos' => $lastOkEvent['pos'],
170  'id' => $lastOkEvent['id'],
171  'ctime' => $curValue ? $curValue['ctime'] : date( 'c' )
172  ];
173  },
175  );
176 
177  $pos = $lastOkEvent['pos'];
178  $id = $lastOkEvent['id'];
179  if ( $ok ) {
180  $this->logger->info( "Updated cache reap position ($pos, $id)." );
181  } else {
182  $this->logger->error( "Could not update cache reap position ($pos, $id)." );
183  }
184  }
185 
186  ScopedCallback::consume( $scopeLock );
187 
188  return $purgeCount;
189  }
190 
194  public function getState() {
195  $posKey = $this->store->makeGlobalKey( 'WANCache', 'reaper', $this->channel );
196 
197  return $this->store->get( $posKey );
198  }
199 }
invoke( $n=100)
Check and reap stale keys based on a chunk of events.
setLogger(LoggerInterface $logger)
Class for scanning through chronological, log-structured data or change logs and locally purging cach...
__construct(WANObjectCache $cache, BagOStuff $store, callable $logCallback, callable $keyCallback, array $params)