MediaWiki REL1_37
RevisionOutputCache.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
26use CacheTime;
28use InvalidArgumentException;
31use MWTimestamp;
33use ParserOutput;
34use Psr\Log\LoggerInterface;
36
45
47 private $name;
48
50 private $cache;
51
57 private $cacheEpoch;
58
64 private $cacheExpiry;
65
67 private $jsonCodec;
68
70 private $stats;
71
73 private $logger;
74
84 public function __construct(
85 string $name,
87 int $cacheExpiry,
88 string $cacheEpoch,
91 LoggerInterface $logger
92 ) {
93 $this->name = $name;
94 $this->cache = $cache;
95 $this->cacheExpiry = $cacheExpiry;
96 $this->cacheEpoch = $cacheEpoch;
97 $this->jsonCodec = $jsonCodec;
98 $this->stats = $stats;
99 $this->logger = $logger;
100 }
101
106 private function incrementStats( RevisionRecord $revision, string $metricSuffix ) {
107 $metricSuffix = str_replace( '.', '_', $metricSuffix );
108 $this->stats->increment( "RevisionOutputCache.{$this->name}.{$metricSuffix}" );
109 }
110
126 public function makeParserOutputKey(
127 RevisionRecord $revision,
128 ParserOptions $options,
129 array $usedOptions = null
130 ): string {
131 $usedOptions = ParserOptions::allCacheVaryingOptions();
132
133 $revId = $revision->getId();
134 $hash = $options->optionsHash( $usedOptions );
135
136 return $this->cache->makeKey( $this->name, $revId, $hash );
137 }
138
148 public function get( RevisionRecord $revision, ParserOptions $parserOptions ) {
149 if ( $this->cacheExpiry <= 0 ) {
150 // disabled
151 return false;
152 }
153
154 if ( !$parserOptions->isSafeToCache() ) {
155 $this->incrementStats( $revision, 'miss.unsafe' );
156 return false;
157 }
158
159 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
160 $json = $this->cache->get( $cacheKey );
161
162 if ( $json === false ) {
163 $this->incrementStats( $revision, 'miss.absent' );
164 return false;
165 }
166
167 $output = $this->restoreFromJson( $json, $cacheKey, ParserOutput::class );
168 if ( $output === null ) {
169 $this->incrementStats( $revision, 'miss.unserialize' );
170 return false;
171 }
172
173 $cacheTime = (int)MWTimestamp::convert( TS_UNIX, $output->getCacheTime() );
174 $expiryTime = (int)MWTimestamp::convert( TS_UNIX, $this->cacheEpoch );
175 $expiryTime = max( $expiryTime, (int)MWTimestamp::now( TS_UNIX ) - $this->cacheExpiry );
176
177 if ( $cacheTime < $expiryTime ) {
178 $this->incrementStats( $revision, 'miss.expired' );
179 return false;
180 }
181
182 $this->logger->debug( 'output cache hit' );
183 $this->incrementStats( $revision, 'hit' );
184 return $output;
185 }
186
193 public function save(
194 ParserOutput $output,
195 RevisionRecord $revision,
196 ParserOptions $parserOptions,
197 string $cacheTime = null
198 ) {
199 if ( !$output->hasText() ) {
200 throw new InvalidArgumentException( 'Attempt to cache a ParserOutput with no text set!' );
201 }
202
203 if ( $this->cacheExpiry <= 0 ) {
204 // disabled
205 return;
206 }
207
208 $cacheKey = $this->makeParserOutputKey( $revision, $parserOptions );
209
210 $output->setCacheTime( $cacheTime ?: wfTimestampNow() );
211 $output->setCacheRevisionId( $revision->getId() );
212
213 // Save the timestamp so that we don't have to load the revision row on view
214 $output->setTimestamp( $revision->getTimestamp() );
215
216 $msg = "Saved in RevisionOutputCache with key $cacheKey" .
217 " and timestamp $cacheTime" .
218 " and revision id {$revision->getId()}.";
219
220 $output->addCacheMessage( $msg );
221
222 // The ParserOutput might be dynamic and have been marked uncacheable by the parser.
223 $output->updateCacheExpiry( $this->cacheExpiry );
224
225 $expiry = $output->getCacheExpiry();
226 if ( $expiry <= 0 ) {
227 $this->incrementStats( $revision, 'save.uncacheable' );
228 return;
229 }
230
231 if ( !$parserOptions->isSafeToCache() ) {
232 $this->incrementStats( $revision, 'save.unsafe' );
233 return;
234 }
235
236 $json = $this->encodeAsJson( $output, $cacheKey );
237 if ( $json === null ) {
238 $this->incrementStats( $revision, 'save.nonserializable' );
239 return;
240 }
241
242 $this->cache->set( $cacheKey, $json, $expiry );
243 $this->incrementStats( $revision, 'save.success' );
244 }
245
252 private function restoreFromJson( string $jsonData, string $key, string $expectedClass ) {
253 try {
255 $obj = $this->jsonCodec->unserialize( $jsonData, $expectedClass );
256 return $obj;
257 } catch ( InvalidArgumentException $e ) {
258 $this->logger->error( 'Unable to unserialize JSON', [
259 'name' => $this->name,
260 'cache_key' => $key,
261 'message' => $e->getMessage()
262 ] );
263 return null;
264 }
265 }
266
272 private function encodeAsJson( CacheTime $obj, string $key ) {
273 try {
274 return $this->jsonCodec->serialize( $obj );
275 } catch ( InvalidArgumentException $e ) {
276 $this->logger->error( 'Unable to serialize JSON', [
277 'name' => $this->name,
278 'cache_key' => $key,
279 'message' => $e->getMessage(),
280 ] );
281 return null;
282 }
283 }
284}
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Parser cache specific expiry check.
Definition CacheTime.php:35
setCacheRevisionId( $id)
updateCacheExpiry( $seconds)
Sets the number of seconds after which this object should expire.
setCacheTime( $t)
setCacheTime() sets the timestamp expressing when the page has been rendered.
Definition CacheTime.php:79
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Library for creating and parsing MW-style timestamps.
Cache for ParserOutput objects.
string $cacheEpoch
Anything cached prior to this is invalidated.
string $name
The name of this cache.
encodeAsJson(CacheTime $obj, string $key)
string $cacheExpiry
Expiry time for cache entries.
save(ParserOutput $output, RevisionRecord $revision, ParserOptions $parserOptions, string $cacheTime=null)
restoreFromJson(string $jsonData, string $key, string $expectedClass)
__construct(string $name, WANObjectCache $cache, int $cacheExpiry, string $cacheEpoch, JsonCodec $jsonCodec, IBufferingStatsdDataFactory $stats, LoggerInterface $logger)
incrementStats(RevisionRecord $revision, string $metricSuffix)
makeParserOutputKey(RevisionRecord $revision, ParserOptions $options, array $usedOptions=null)
Get a key that will be used by this cache to store the content for a given page considering the given...
Page revision base class.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getId( $wikiId=self::LOCAL)
Get revision ID.
Set options of the Parser.
isSafeToCache(array $usedOptions=null)
Test whether these options are safe to cache.
optionsHash( $forOptions, $title=null)
Generate a hash string with the values set on these ParserOptions for the keys given in the array.
hasText()
Returns true if text was passed to the constructor, or set using setText().
addCacheMessage(string $msg)
Adds a comment notice about cache state to the text of the page.
setTimestamp( $timestamp)
Multi-datacenter aware caching interface.
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.