26use InvalidArgumentException;
31use Psr\Log\LoggerInterface;
93 LoggerInterface $logger,
97 $this->cache = $cache;
98 $this->cacheExpiry = $cacheExpiry;
99 $this->cacheEpoch = $cacheEpoch;
100 $this->jsonCodec = $jsonCodec;
101 $this->stats = $stats;
102 $this->logger = $logger;
103 $this->globalIdGenerator = $globalIdGenerator;
110 private function incrementStats(
string $status, ?
string $reason =
null ) {
111 $metricSuffix = $reason ?
"{$status}_{$reason}" : $status;
113 $this->stats->getCounter(
'RevisionOutputCache_operation_total' )
114 ->setLabel(
'name', $this->name )
115 ->setLabel(
'status', $status )
116 ->setLabel(
'reason', $reason ?:
'n/a' )
117 ->copyToStatsdAt(
"RevisionOutputCache.{$this->name}.{$metricSuffix}" )
142 ?array $usedOptions =
null
146 $revId = $revision->
getId();
149 throw new InvalidArgumentException(
"Revision must have an id number" );
152 return $this->cache->makeKey( $this->name, $revId, $hash );
175 ?array $usedOptions =
null
180 $revId = (string)$revision->
getId();
182 return $this->cache->makeKey( $this->name, $revId, $hash );
195 if ( $this->cacheExpiry <= 0 ) {
200 if ( !$parserOptions->isSafeToCache() ) {
201 $this->incrementStats(
'miss',
'unsafe' );
205 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
206 $json = $this->cache->get( $cacheKey );
208 if ( $json ===
false ) {
209 $this->incrementStats(
'miss',
'absent' );
213 $output = $this->restoreFromJson( $json, $cacheKey, ParserOutput::class );
214 if ( $output ===
null ) {
215 $this->incrementStats(
'miss',
'unserialize' );
219 $cacheTime = (int)MWTimestamp::convert( TS_UNIX, $output->getCacheTime() );
220 $expiryTime = (int)MWTimestamp::convert( TS_UNIX, $this->cacheEpoch );
221 $expiryTime = max( $expiryTime, (
int)MWTimestamp::now( TS_UNIX ) - $this->cacheExpiry );
223 if ( $cacheTime < $expiryTime ) {
224 $this->incrementStats(
'miss',
'expired' );
228 $this->logger->debug(
'old-revision cache hit' );
229 $this->incrementStats(
'hit' );
243 ?
string $cacheTime =
null
246 throw new InvalidArgumentException(
'Attempt to cache a ParserOutput with no text set!' );
249 if ( $this->cacheExpiry <= 0 ) {
254 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
269 $output->
setRenderId( $this->globalIdGenerator->newUUIDv1() );
275 $msg =
"Saved in RevisionOutputCache with key $cacheKey" .
276 " and timestamp $cacheTime" .
277 " and revision id {$revision->getId()}.";
285 if ( $expiry <= 0 ) {
286 $this->incrementStats(
'save',
'uncacheable' );
291 $this->incrementStats(
'save',
'unsafe' );
295 $json = $this->encodeAsJson( $output, $cacheKey );
296 if ( $json ===
null ) {
297 $this->incrementStats(
'save',
'nonserializable' );
301 $this->cache->set( $cacheKey, $json, $expiry );
302 $this->incrementStats(
'save',
'success' );
311 private function restoreFromJson(
string $jsonData,
string $key,
string $expectedClass ) {
314 $obj = $this->jsonCodec->deserialize( $jsonData, $expectedClass );
316 }
catch ( JsonException $e ) {
317 $this->logger->error(
'Unable to deserialize JSON', [
318 'name' => $this->name,
320 'message' => $e->getMessage()
331 private function encodeAsJson( CacheTime $obj,
string $key ) {
333 return $this->jsonCodec->serialize( $obj );
334 }
catch ( JsonException $e ) {
335 $this->logger->error(
'Unable to serialize JSON', [
336 'name' => $this->name,
338 'message' => $e->getMessage(),