26use InvalidArgumentException;
32use Psr\Log\LoggerInterface;
94 LoggerInterface $logger,
98 $this->cache = $cache;
99 $this->cacheExpiry = $cacheExpiry;
100 $this->cacheEpoch = $cacheEpoch;
101 $this->jsonCodec = $jsonCodec;
102 $this->stats = $stats;
103 $this->logger = $logger;
104 $this->globalIdGenerator = $globalIdGenerator;
111 private function getContentModelFromRevision(
RevisionRecord $revision ) {
112 if ( !$revision->
hasSlot( SlotRecord::MAIN ) ) {
123 private function incrementStats( RevisionRecord $revision,
string $status, ?
string $reason =
null ) {
124 $contentModel = $this->getContentModelFromRevision( $revision );
125 $metricSuffix = $reason ?
"{$status}_{$reason}" : $status;
127 $this->stats->getCounter(
'RevisionOutputCache_operation_total' )
128 ->setLabel(
'name', $this->name )
129 ->setLabel(
'contentModel', $contentModel )
130 ->setLabel(
'status', $status )
131 ->setLabel(
'reason', $reason ?:
'n/a' )
132 ->copyToStatsdAt(
"RevisionOutputCache.{$this->name}.{$metricSuffix}" )
140 private function incrementRenderReasonStats( RevisionRecord $revision, $renderReason ) {
141 $contentModel = $this->getContentModelFromRevision( $revision );
142 $renderReason = preg_replace(
'/\W+/',
'_', $renderReason );
144 $this->stats->getCounter(
'RevisionOutputCache_render_total' )
145 ->setLabel(
'name', $this->name )
146 ->setLabel(
'contentModel', $contentModel )
147 ->setLabel(
'reason', $renderReason )
172 ?array $usedOptions =
null
176 $revId = $revision->
getId();
179 throw new InvalidArgumentException(
"Revision must have an id number" );
182 return $this->cache->makeKey( $this->name, $revId, $hash );
205 ?array $usedOptions =
null
210 $revId = (string)$revision->
getId();
212 return $this->cache->makeKey( $this->name, $revId, $hash );
225 if ( $this->cacheExpiry <= 0 ) {
230 if ( !$parserOptions->isSafeToCache() ) {
231 $this->incrementStats( $revision,
'miss',
'unsafe' );
235 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
236 $json = $this->cache->get( $cacheKey );
238 if ( $json ===
false ) {
239 $this->incrementStats( $revision,
'miss',
'absent' );
243 $output = $this->restoreFromJson( $json, $cacheKey );
244 if ( $output ===
null ) {
245 $this->incrementStats( $revision,
'miss',
'unserialize' );
249 $cacheTime = (int)MWTimestamp::convert( TS_UNIX, $output->getCacheTime() );
250 $expiryTime = (int)MWTimestamp::convert( TS_UNIX, $this->cacheEpoch );
251 $expiryTime = max( $expiryTime, (
int)MWTimestamp::now( TS_UNIX ) - $this->cacheExpiry );
253 if ( $cacheTime < $expiryTime ) {
254 $this->incrementStats( $revision,
'miss',
'expired' );
258 $this->logger->debug(
'old-revision cache hit' );
259 $this->incrementStats( $revision,
'hit' );
273 ?
string $cacheTime =
null
276 throw new InvalidArgumentException(
'Attempt to cache a ParserOutput with no text set!' );
279 if ( $this->cacheExpiry <= 0 ) {
284 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
299 $output->
setRenderId( $this->globalIdGenerator->newUUIDv1() );
305 $msg =
"Saved in RevisionOutputCache with key $cacheKey" .
306 " and timestamp $cacheTime" .
307 " and revision id {$revision->getId()}.";
315 if ( $expiry <= 0 ) {
316 $this->incrementStats( $revision,
'save',
'uncacheable' );
321 $this->incrementStats( $revision,
'save',
'unsafe' );
325 $json = $this->encodeAsJson( $output, $cacheKey );
326 if ( $json ===
null ) {
327 $this->incrementStats( $revision,
'save',
'nonserializable' );
331 $this->cache->set( $cacheKey, $json, $expiry );
332 $this->incrementStats( $revision,
'save',
'success' );
333 $this->incrementRenderReasonStats( $revision, $parserOptions->
getRenderReason() );
341 private function restoreFromJson(
string $jsonData,
string $key ) {
344 $obj = $this->jsonCodec->deserialize( $jsonData, ParserOutput::class );
346 }
catch ( JsonException $e ) {
347 $this->logger->error(
'Unable to deserialize JSON', [
348 'name' => $this->name,
350 'message' => $e->getMessage()
361 private function encodeAsJson( CacheTime $obj,
string $key ) {
363 return $this->jsonCodec->serialize( $obj );
364 }
catch ( JsonException $e ) {
365 $this->logger->error(
'Unable to serialize JSON', [
366 'name' => $this->name,
368 'message' => $e->getMessage(),