12use InvalidArgumentException;
18use Psr\Log\LoggerInterface;
21use Wikimedia\Timestamp\TimestampFormat as TS;
67 private readonly LoggerInterface $logger,
71 $this->cacheExpiry = $cacheExpiry;
72 $this->cacheEpoch = $cacheEpoch;
79 private function getContentModelFromRevision(
RevisionRecord $revision ) {
80 if ( !$revision->
hasSlot( SlotRecord::MAIN ) ) {
91 private function incrementStats( RevisionRecord $revision,
string $status, ?
string $reason =
null ) {
92 $contentModel = $this->getContentModelFromRevision( $revision );
94 $this->stats->getCounter(
'RevisionOutputCache_operation_total' )
95 ->setLabel(
'name', $this->name )
96 ->setLabel(
'contentModel', $contentModel )
97 ->setLabel(
'status', $status )
98 ->setLabel(
'reason', $reason ?:
'n/a' )
106 private function incrementRenderReasonStats( RevisionRecord $revision, $renderReason ) {
107 $contentModel = $this->getContentModelFromRevision( $revision );
108 $renderReason = preg_replace(
'/\W+/',
'_', $renderReason );
110 $this->stats->getCounter(
'RevisionOutputCache_render_total' )
111 ->setLabel(
'name', $this->name )
112 ->setLabel(
'contentModel', $contentModel )
113 ->setLabel(
'reason', $renderReason )
138 ?array $usedOptions =
null
142 $revId = $revision->
getId();
145 throw new InvalidArgumentException(
"Revision must have an id number" );
148 return $this->cache->makeKey( $this->name, $revId, $hash );
171 ?array $usedOptions =
null
176 $revId = (string)$revision->
getId();
178 return $this->cache->makeKey( $this->name, $revId, $hash );
191 if ( $this->cacheExpiry <= 0 ) {
196 if ( !$parserOptions->isSafeToCache() ) {
197 $this->incrementStats( $revision,
'miss',
'unsafe' );
201 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
202 $json = $this->cache->get( $cacheKey );
204 if ( $json ===
false ) {
205 $this->incrementStats( $revision,
'miss',
'absent' );
209 $output = $this->restoreFromJson( $json, $cacheKey );
210 if ( $output ===
null ) {
211 $this->incrementStats( $revision,
'miss',
'unserialize' );
215 $cacheTime = (int)MWTimestamp::convert( TS::UNIX, $output->getCacheTime() );
216 $expiryTime = (int)MWTimestamp::convert( TS::UNIX, $this->cacheEpoch );
217 $expiryTime = max( $expiryTime, (
int)MWTimestamp::now( TS::UNIX ) - $this->cacheExpiry );
219 if ( $cacheTime < $expiryTime ) {
220 $this->incrementStats( $revision,
'miss',
'expired' );
224 $this->logger->debug(
'old-revision cache hit' );
225 $this->incrementStats( $revision,
'hit' );
239 ?
string $cacheTime =
null
242 throw new InvalidArgumentException(
'Attempt to cache a ParserOutput with no text set!' );
245 if ( $this->cacheExpiry <= 0 ) {
250 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
265 $output->
setRenderId( $this->globalIdGenerator->newUUIDv1() );
271 $msg =
"Saved in RevisionOutputCache with key $cacheKey" .
272 " and timestamp $cacheTime" .
273 " and revision id {$revision->getId()}.";
281 if ( $expiry <= 0 ) {
282 $this->incrementStats( $revision,
'save',
'uncacheable' );
287 $this->incrementStats( $revision,
'save',
'unsafe' );
291 $json = $this->encodeAsJson( $output, $cacheKey );
292 if ( $json ===
null ) {
293 $this->incrementStats( $revision,
'save',
'nonserializable' );
297 $this->cache->set( $cacheKey, $json, $expiry );
298 $this->incrementStats( $revision,
'save',
'success' );
299 $this->incrementRenderReasonStats( $revision, $parserOptions->
getRenderReason() );
307 private function restoreFromJson(
string $jsonData,
string $key ) {
310 $obj = $this->jsonCodec->deserialize( $jsonData, ParserOutput::class );
312 }
catch ( JsonException $e ) {
313 $this->logger->error(
'Unable to deserialize JSON', [
314 'name' => $this->name,
316 'message' => $e->getMessage()
327 private function encodeAsJson( CacheTime $obj,
string $key ) {
329 return $this->jsonCodec->serialize( $obj );
330 }
catch ( JsonException $e ) {
331 $this->logger->error(
'Unable to serialize JSON', [
332 'name' => $this->name,
334 'message' => $e->getMessage(),