29use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
31use Psr\Log\LoggerInterface;
36use Wikimedia\ScopedCallback;
82 StatsdDataFactoryInterface
$stats,
112 $dbw = $this->lb->getConnectionRef(
DB_MASTER );
113 if ( !$dbw->lock( $key, $fname, 0 ) ) {
118 $unlocker =
new ScopedCallback(
function () use ( $dbw, $key, $fname ) {
119 $dbw->unlock( $key, $fname );
126 if ( $editInfo &&
wfTimestamp( TS_UNIX, $editInfo->timestamp ) >= $cutoffTime ) {
127 $alreadyCached =
true;
129 $format =
$content->getDefaultFormat();
131 $editInfo->output->setCacheTime( $editInfo->timestamp );
132 $alreadyCached =
false;
135 $context = [
'cachekey' => $key,
'title' =>
$title->getPrefixedText() ];
137 if ( $editInfo && $editInfo->output ) {
139 Hooks::run(
'ParserOutputStashForEdit',
140 [ $page,
$content, $editInfo->output, $summary, $user ] );
142 if ( $alreadyCached ) {
143 $logger->debug(
"Parser output for key '{cachekey}' already cached.",
$context );
150 $editInfo->pstContent,
152 $editInfo->timestamp,
156 if ( $code ===
true ) {
157 $logger->debug(
"Cached parser output for key '{cachekey}'.",
$context );
160 } elseif ( $code ===
'uncacheable' ) {
162 "Uncacheable parser output for key '{cachekey}' [{code}].",
169 "Failed to cache parser output for key '{cachekey}'.",
205 $this->initiator !== self::INITIATOR_USER ||
218 'title' =>
$title->getPrefixedText(),
223 if ( !is_object( $editInfo ) || !$editInfo->output ) {
226 $logger->info(
"Empty cache for key '{key}' but not for user.",
$context );
234 $age = time() - (int)
wfTimestamp( TS_UNIX, $editInfo->output->getCacheTime() );
237 $isCacheUsable =
true;
238 if ( $age <= self::PRESUME_FRESH_TTL_SEC ) {
241 $logger->debug(
"Timestamp-based cache hit for key '{key}'.",
$context );
242 } elseif ( $user->
isAnon() ) {
244 $cacheTime = $editInfo->output->getCacheTime();
245 if ( $lastEdit < $cacheTime ) {
248 $logger->debug(
"Edit check based cache hit for key '{key}'.",
$context );
250 $isCacheUsable =
false;
252 $logger->info(
"Stale cache for key '{key}' due to outside edits.",
$context );
258 $logger->debug(
"Edit count based cache hit for key '{key}'.",
$context );
260 $isCacheUsable =
false;
262 $logger->info(
"Stale cache for key '{key}'due to outside edits.",
$context );
266 if ( !$isCacheUsable ) {
270 if ( $editInfo->output->getFlag(
'vary-revision' ) ) {
274 "Cache for key '{key}' has vary-revision; post-insertion parse inevitable.",
278 static $flagsMaybeReparse = [
282 'vary-revision-timestamp',
284 'vary-revision-sha1',
288 foreach ( $flagsMaybeReparse as $flag ) {
289 if ( $editInfo->output->getFlag( $flag ) ) {
291 "Cache for key '{key}' has $flag; post-insertion parse possible.",
306 $this->stats->increment(
'editstash.' . $subkey );
307 $this->stats->increment(
'editstash_by_model.' .
$content->getModel() .
'.' . $subkey );
318 $start = microtime(
true );
323 $dbw = $this->lb->getAnyOpenConnection( $this->lb->getWriterIndex() );
324 if ( $dbw && $dbw->lock( $key, __METHOD__, 30 ) ) {
326 $dbw->unlock( $key, __METHOD__ );
329 $timeMs = 1000 * max( 0, microtime(
true ) - $start );
330 $this->stats->timing(
'editstash.lock_wait_time', $timeMs );
341 $textKey = $this->cache->makeKey(
'stashedit',
'text', $textHash );
343 return $this->cache->get( $textKey );
352 $textKey = $this->cache->makeKey(
'stashedit',
'text', $textHash );
354 return $this->cache->set(
358 BagOStuff::WRITE_ALLOW_SEGMENTS
367 $db = $this->lb->getConnectionRef(
DB_REPLICA );
369 $actorQuery = ActorMigration::newMigration()->getWhere( $db,
'rc_user', $user,
false );
370 $time = $db->selectField(
371 [
'recentchanges' ] + $actorQuery[
'tables'],
373 [ $actorQuery[
'conds'] ],
389 return sha1( implode(
"\n", [
409 return $this->cache->makeKey(
411 md5(
$title->getPrefixedDBkey() ),
424 $stashInfo = $this->cache->get( $key );
425 if ( is_object( $stashInfo ) && $stashInfo->output instanceof
ParserOutput ) {
454 $ttl = min( $parserOutput->
getCacheExpiry() - $age, self::MAX_CACHE_TTL );
456 if ( $parserOutput->
getFlag(
'user-signature' ) ) {
457 $ttl = min( $ttl, self::MAX_SIGNATURE_TTL );
461 return 'uncacheable';
465 $stashInfo = (object)[
466 'pstContent' => $pstContent,
467 'output' => $parserOutput,
468 'timestamp' => $timestamp,
472 $ok = $this->cache->set( $key, $stashInfo, $ttl, BagOStuff::WRITE_ALLOW_SEGMENTS );
478 return $ok ?
true :
'store_error';
486 $key = $this->cache->makeKey(
'stash-edit-recent', sha1( $user->
getName() ) );
488 $keyList = $this->cache->get( $key ) ?: [];
489 if ( count( $keyList ) >= self::MAX_CACHE_RECENT ) {
490 $oldestKey = array_shift( $keyList );
491 $this->cache->delete( $oldestKey, BagOStuff::WRITE_PRUNE_SEGMENTS );
494 $keyList[] = $newKey;
495 $this->cache->set( $key, $keyList, 2 * self::MAX_CACHE_TTL );
503 $key = $this->cache->makeKey(
'stash-edit-recent', sha1( $user->
getName() ) );
505 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.
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
recentStashEntryCount(User $user)
parseAndCache(WikiPage $page, Content $content, User $user, $summary)
pruneExcessStashedEntries(User $user, $newKey)
getContentHash(Content $content)
Get hash of the content, factoring in model/format.
__construct(BagOStuff $cache, ILoadBalancer $lb, LoggerInterface $logger, StatsdDataFactoryInterface $stats, $initiator)
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
stashInputText( $text, $textHash)
const INITIATOR_JOB_OR_CLI
getAndWaitForStashValue( $key)
fetchInputText( $textHash)
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.