MediaWiki  master
RevisionOutputCache.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Parser;
25 
26 use CacheTime;
28 use InvalidArgumentException;
31 use MWTimestamp;
32 use ParserOptions;
33 use ParserOutput;
34 use Psr\Log\LoggerInterface;
35 use WANObjectCache;
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,
89  JsonCodec $jsonCodec,
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 }
ParserOutput\addCacheMessage
addCacheMessage(string $msg)
Adds a comment notice about cache state to the text of the page.
Definition: ParserOutput.php:463
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:44
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:37
CacheTime\getCacheExpiry
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Definition: CacheTime.php:138
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
ParserOutput
Definition: ParserOutput.php:31
CacheTime
Parser cache specific expiry check.
Definition: CacheTime.php:35
Parser\RevisionOutputCache\$name
string $name
The name of this cache.
Definition: RevisionOutputCache.php:47
ParserOutput\setTimestamp
setTimestamp( $timestamp)
Definition: ParserOutput.php:752
Parser\RevisionOutputCache\save
save(ParserOutput $output, RevisionRecord $revision, ParserOptions $parserOptions, string $cacheTime=null)
Definition: RevisionOutputCache.php:193
Parser\RevisionOutputCache\restoreFromJson
restoreFromJson(string $jsonData, string $key, string $expectedClass)
Definition: RevisionOutputCache.php:252
MediaWiki\Parser
Definition: ParserCacheFactory.php:22
Parser\RevisionOutputCache\$cacheEpoch
string $cacheEpoch
Anything cached prior to this is invalidated.
Definition: RevisionOutputCache.php:57
CacheTime\setCacheTime
setCacheTime( $t)
setCacheTime() sets the timestamp expressing when the page has been rendered.
Definition: CacheTime.php:79
Parser\RevisionOutputCache\$logger
LoggerInterface $logger
Definition: RevisionOutputCache.php:73
Parser\RevisionOutputCache\makeParserOutputKey
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...
Definition: RevisionOutputCache.php:126
Parser\RevisionOutputCache\__construct
__construct(string $name, WANObjectCache $cache, int $cacheExpiry, string $cacheEpoch, JsonCodec $jsonCodec, IBufferingStatsdDataFactory $stats, LoggerInterface $logger)
Definition: RevisionOutputCache.php:84
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1861
Parser\RevisionOutputCache\$jsonCodec
JsonCodec $jsonCodec
Definition: RevisionOutputCache.php:67
Parser\RevisionOutputCache\encodeAsJson
encodeAsJson(CacheTime $obj, string $key)
Definition: RevisionOutputCache.php:272
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
Parser\RevisionOutputCache\incrementStats
incrementStats(RevisionRecord $revision, string $metricSuffix)
Definition: RevisionOutputCache.php:106
Parser\RevisionOutputCache\$stats
IBufferingStatsdDataFactory $stats
Definition: RevisionOutputCache.php:70
Parser\RevisionOutputCache\$cacheExpiry
string $cacheExpiry
Expiry time for cache entries.
Definition: RevisionOutputCache.php:64
IBufferingStatsdDataFactory
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Definition: IBufferingStatsdDataFactory.php:13
Parser\RevisionOutputCache
Cache for ParserOutput objects.
Definition: RevisionOutputCache.php:44
Parser\RevisionOutputCache\$cache
WANObjectCache $cache
Definition: RevisionOutputCache.php:50
ParserOptions\allCacheVaryingOptions
static allCacheVaryingOptions()
Return all option keys that vary the options hash.
Definition: ParserOptions.php:1431
ParserOutput\hasText
hasText()
Returns true if text was passed to the constructor, or set using setText().
Definition: ParserOutput.php:304
CacheTime\setCacheRevisionId
setCacheRevisionId( $id)
Definition: CacheTime.php:104
CacheTime\updateCacheExpiry
updateCacheExpiry( $seconds)
Sets the number of seconds after which this object should expire.
Definition: CacheTime.php:121
ParserOptions\isSafeToCache
isSafeToCache(array $usedOptions=null)
Test whether these options are safe to cache.
Definition: ParserOptions.php:1528
MediaWiki\Json\JsonCodec
Definition: JsonCodec.php:36
ParserOptions\optionsHash
optionsHash( $forOptions, $title=null)
Generate a hash string with the values set on these ParserOptions for the keys given in the array.
Definition: ParserOptions.php:1468