28use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
33use Psr\Log\LoggerInterface;
38use Wikimedia\ScopedCallback;
87 StatsdDataFactoryInterface
$stats,
95 $this->hookRunner =
new HookRunner( $hookContainer );
119 $dbw = $this->lb->getConnectionRef(
DB_MASTER );
120 if ( !$dbw->lock( $key, $fname, 0 ) ) {
125 $unlocker =
new ScopedCallback(
function () use ( $dbw, $key, $fname ) {
126 $dbw->unlock( $key, $fname );
133 if ( $editInfo &&
wfTimestamp( TS_UNIX, $editInfo->timestamp ) >= $cutoffTime ) {
134 $alreadyCached =
true;
136 $format =
$content->getDefaultFormat();
138 $editInfo->output->setCacheTime( $editInfo->timestamp );
139 $alreadyCached =
false;
142 $context = [
'cachekey' => $key,
'title' =>
$title->getPrefixedText() ];
144 if ( $editInfo && $editInfo->output ) {
146 $this->hookRunner->onParserOutputStashForEdit(
147 $page,
$content, $editInfo->output, $summary, $user );
149 if ( $alreadyCached ) {
150 $logger->debug(
"Parser output for key '{cachekey}' already cached.", $context );
157 $editInfo->pstContent,
159 $editInfo->timestamp,
163 if ( $code ===
true ) {
164 $logger->debug(
"Cached parser output for key '{cachekey}'.", $context );
167 } elseif ( $code ===
'uncacheable' ) {
169 "Uncacheable parser output for key '{cachekey}' [{code}].",
170 $context + [
'code' => $code ]
176 "Failed to cache parser output for key '{cachekey}'.",
177 $context + [
'code' => $code ]
212 $this->initiator !== self::INITIATOR_USER ||
225 'title' =>
$title->getPrefixedText(),
230 if ( !is_object( $editInfo ) || !$editInfo->output ) {
233 $logger->info(
"Empty cache for key '{key}' but not for user.", $context );
235 $logger->debug(
"Empty cache for key '{key}'.", $context );
241 $age = time() - (int)
wfTimestamp( TS_UNIX, $editInfo->output->getCacheTime() );
242 $context[
'age'] = $age;
244 $isCacheUsable =
true;
245 if ( $age <= self::PRESUME_FRESH_TTL_SEC ) {
248 $logger->debug(
"Timestamp-based cache hit for key '{key}'.", $context );
249 } elseif ( $user->
isAnon() ) {
251 $cacheTime = $editInfo->output->getCacheTime();
252 if ( $lastEdit < $cacheTime ) {
255 $logger->debug(
"Edit check based cache hit for key '{key}'.", $context );
257 $isCacheUsable =
false;
259 $logger->info(
"Stale cache for key '{key}' due to outside edits.", $context );
265 $logger->debug(
"Edit count based cache hit for key '{key}'.", $context );
267 $isCacheUsable =
false;
269 $logger->info(
"Stale cache for key '{key}'due to outside edits.", $context );
273 if ( !$isCacheUsable ) {
277 if ( $editInfo->output->getFlag(
'vary-revision' ) ) {
281 "Cache for key '{key}' has vary-revision; post-insertion parse inevitable.",
285 static $flagsMaybeReparse = [
289 'vary-revision-timestamp',
291 'vary-revision-sha1',
295 foreach ( $flagsMaybeReparse as $flag ) {
296 if ( $editInfo->output->getFlag( $flag ) ) {
298 "Cache for key '{key}' has $flag; post-insertion parse possible.",
313 $this->stats->increment(
'editstash.' . $subkey );
314 $this->stats->increment(
'editstash_by_model.' .
$content->getModel() .
'.' . $subkey );
325 $start = microtime(
true );
330 $dbw = $this->lb->getAnyOpenConnection( $this->lb->getWriterIndex() );
331 if ( $dbw && $dbw->lock( $key, __METHOD__, 30 ) ) {
333 $dbw->unlock( $key, __METHOD__ );
336 $timeMs = 1000 * max( 0, microtime(
true ) - $start );
337 $this->stats->timing(
'editstash.lock_wait_time', $timeMs );
348 $textKey = $this->cache->makeKey(
'stashedit',
'text', $textHash );
350 return $this->cache->get( $textKey );
359 $textKey = $this->cache->makeKey(
'stashedit',
'text', $textHash );
361 return $this->cache->set(
365 BagOStuff::WRITE_ALLOW_SEGMENTS
374 $db = $this->lb->getConnectionRef(
DB_REPLICA );
376 $actorQuery = ActorMigration::newMigration()->getWhere( $db,
'rc_user', $user,
false );
377 $time = $db->selectField(
378 [
'recentchanges' ] + $actorQuery[
'tables'],
380 [ $actorQuery[
'conds'] ],
396 return sha1( implode(
"\n", [
416 return $this->cache->makeKey(
418 md5(
$title->getPrefixedDBkey() ),
431 $stashInfo = $this->cache->get( $key );
432 if ( is_object( $stashInfo ) && $stashInfo->output instanceof
ParserOutput ) {
461 $ttl = min( $parserOutput->
getCacheExpiry() - $age, self::MAX_CACHE_TTL );
463 if ( $parserOutput->
getFlag(
'user-signature' ) ) {
464 $ttl = min( $ttl, self::MAX_SIGNATURE_TTL );
468 return 'uncacheable';
472 $stashInfo = (object)[
473 'pstContent' => $pstContent,
474 'output' => $parserOutput,
475 'timestamp' => $timestamp,
479 $ok = $this->cache->set( $key, $stashInfo, $ttl, BagOStuff::WRITE_ALLOW_SEGMENTS );
485 return $ok ?
true :
'store_error';
493 $key = $this->cache->makeKey(
'stash-edit-recent', sha1( $user->
getName() ) );
495 $keyList = $this->cache->get( $key ) ?: [];
496 if ( count( $keyList ) >= self::MAX_CACHE_RECENT ) {
497 $oldestKey = array_shift( $keyList );
498 $this->cache->delete( $oldestKey, BagOStuff::WRITE_PRUNE_SEGMENTS );
501 $keyList[] = $newKey;
502 $this->cache->set( $key, $keyList, 2 * self::MAX_CACHE_TTL );
510 $key = $this->cache->makeKey(
'stash-edit-recent', sha1( $user->
getName() ) );
512 return count( $this->cache->get( $key ) ?: [] );
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
This class handles the logic for the actor table migration and should always be used in lieu of direc...
Class representing a cache/ephemeral data store.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Class for managing stashed edits used by the page updater classes.
incrStatsByContent( $subkey, Content $content)
StatsdDataFactoryInterface $stats
ParserOutputStashForEditHook $hookRunner
recentStashEntryCount(User $user)
pruneExcessStashedEntries(User $user, $newKey)
getContentHash(Content $content)
Get hash of the content, factoring in model/format.
getStashKey(Title $title, $contentHash, User $user)
Get the temporary prepared edit stash key for a user.
checkCache(Title $title, Content $content, User $user)
Check that a prepared edit is in cache and still up-to-date.
storeStashValue( $key, Content $pstContent, ParserOutput $parserOutput, $timestamp, User $user)
Build a value to store in memcached based on the PST content and parser output.
const PRESUME_FRESH_TTL_SEC
parseAndCache(WikiPage $page, Content $content, User $user, string $summary)
stashInputText( $text, $textHash)
const INITIATOR_JOB_OR_CLI
getAndWaitForStashValue( $key)
fetchInputText( $textHash)
__construct(BagOStuff $cache, ILoadBalancer $lb, LoggerInterface $logger, StatsdDataFactoryInterface $stats, HookContainer $hookContainer, $initiator)
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
getRequest()
Get the WebRequest object to use with this object.
getName()
Get the user name, or the IP of an anonymous user.
getId()
Get the user's ID.
getEditCount()
Get the user's edit count.
isAnon()
Get whether the user is anonymous.
Class representing a MediaWiki article and history.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Prepare content which is about to be saved.
getTitle()
Get the title object of the article.
Base interface for content objects.