// the stash request finishes parsing. For the lock acquisition below, there is not much
// need to duplicate parsing of the same content/user/summary bundle, so try to avoid
// blocking at all here.
- $dbw = $this->lb->getConnection( DB_MASTER );
+ $dbw = $this->lb->getConnectionRef( DB_MASTER );
if ( !$dbw->lock( $key, $fname, 0 ) ) {
// De-duplicate requests on the same key
return self::ERROR_BUSY;
$editInfo = $this->getAndWaitForStashValue( $key );
if ( !is_object( $editInfo ) || !$editInfo->output ) {
- $this->stats->increment( 'editstash.cache_misses.no_stash' );
+ $this->incrStatsByContent( 'cache_misses.no_stash', $content );
if ( $this->recentStashEntryCount( $user ) > 0 ) {
$logger->info( "Empty cache for key '{key}' but not for user.", $context );
} else {
$isCacheUsable = true;
if ( $age <= self::PRESUME_FRESH_TTL_SEC ) {
// Assume nothing changed in this time
- $this->stats->increment( 'editstash.cache_hits.presumed_fresh' );
+ $this->incrStatsByContent( 'cache_hits.presumed_fresh', $content );
$logger->debug( "Timestamp-based cache hit for key '{key}'.", $context );
} elseif ( $user->isAnon() ) {
$lastEdit = $this->lastEditTime( $user );
$cacheTime = $editInfo->output->getCacheTime();
if ( $lastEdit < $cacheTime ) {
// Logged-out user made no local upload/template edits in the meantime
- $this->stats->increment( 'editstash.cache_hits.presumed_fresh' );
+ $this->incrStatsByContent( 'cache_hits.presumed_fresh', $content );
$logger->debug( "Edit check based cache hit for key '{key}'.", $context );
} else {
$isCacheUsable = false;
- $this->stats->increment( 'editstash.cache_misses.proven_stale' );
+ $this->incrStatsByContent( 'cache_misses.proven_stale', $content );
$logger->info( "Stale cache for key '{key}' due to outside edits.", $context );
}
} else {
if ( $editInfo->edits === $user->getEditCount() ) {
// Logged-in user made no local upload/template edits in the meantime
- $this->stats->increment( 'editstash.cache_hits.presumed_fresh' );
+ $this->incrStatsByContent( 'cache_hits.presumed_fresh', $content );
$logger->debug( "Edit count based cache hit for key '{key}'.", $context );
} else {
$isCacheUsable = false;
- $this->stats->increment( 'editstash.cache_misses.proven_stale' );
+ $this->incrStatsByContent( 'cache_misses.proven_stale', $content );
$logger->info( "Stale cache for key '{key}'due to outside edits.", $context );
}
}
if ( $editInfo->output->getFlag( 'vary-revision' ) ) {
// This can be used for the initial parse, e.g. for filters or doEditContent(),
- // but a second parse will be triggered in doEditUpdates(). This is not optimal.
+ // but a second parse will be triggered in doEditUpdates() no matter what
$logger->info(
- "Cache for key '{key}' has vary_revision; post-insertion parse inevitable.",
- $context
- );
- } elseif ( $editInfo->output->getFlag( 'vary-revision-id' ) ) {
- // Similar to the above if we didn't guess the ID correctly.
- $logger->debug(
- "Cache for key '{key}' has vary_revision_id; post-insertion parse possible.",
+ "Cache for key '{key}' has 'vary-revision'; post-insertion parse inevitable.",
$context
);
+ } else {
+ static $flagsMaybeReparse = [
+ // Similar to the above if we didn't guess the ID correctly
+ 'vary-revision-id',
+ // Similar to the above if we didn't guess the timestamp correctly
+ 'vary-revision-timestamp',
+ // Similar to the above if we didn't guess the content correctly
+ 'vary-revision-sha1'
+ ];
+ foreach ( $flagsMaybeReparse as $flag ) {
+ if ( $editInfo->output->getFlag( $flag ) ) {
+ $logger->debug(
+ "Cache for key '{key}' has $flag; post-insertion parse possible.",
+ $context
+ );
+ }
+ }
}
return $editInfo;
}
+ /**
+ * @param string $subkey
+ * @param Content $content
+ */
+ private function incrStatsByContent( $subkey, Content $content ) {
+ $this->stats->increment( 'editstash.' . $subkey ); // overall for b/c
+ $this->stats->increment( 'editstash_by_model.' . $content->getModel() . '.' . $subkey );
+ }
+
/**
* @param string $key
* @return bool|stdClass
public function stashInputText( $text, $textHash ) {
$textKey = $this->cache->makeKey( 'stashedit', 'text', $textHash );
- return $this->cache->set( $textKey, $text, self::MAX_CACHE_TTL );
+ return $this->cache->set(
+ $textKey,
+ $text,
+ self::MAX_CACHE_TTL,
+ BagOStuff::WRITE_ALLOW_SEGMENTS
+ );
}
/**
* @return string|null TS_MW timestamp or null
*/
private function lastEditTime( User $user ) {
- $db = $this->lb->getConnection( DB_REPLICA );
+ $db = $this->lb->getConnectionRef( DB_REPLICA );
+
$actorQuery = ActorMigration::newMigration()->getWhere( $db, 'rc_user', $user, false );
$time = $db->selectField(
[ 'recentchanges' ] + $actorQuery['tables'],
*/
private function getStashKey( Title $title, $contentHash, User $user ) {
return $this->cache->makeKey(
- 'stashed-edit-info',
+ 'stashedit-info-v1',
md5( $title->getPrefixedDBkey() ),
// Account for the edit model/text
$contentHash,
);
}
- /**
- * @param string $hash
- * @return string
- */
- private function getStashParserOutputKey( $hash ) {
- return $this->cache->makeKey( 'stashed-edit-output', $hash );
- }
-
/**
* @param string $key
* @return stdClass|bool Object map (pstContent,output,outputID,timestamp,edits) or false
*/
private function getStashValue( $key ) {
$stashInfo = $this->cache->get( $key );
- if ( !is_object( $stashInfo ) ) {
- return false;
- }
-
- $parserOutputKey = $this->getStashParserOutputKey( $stashInfo->outputID );
- $parserOutput = $this->cache->get( $parserOutputKey );
- if ( $parserOutput instanceof ParserOutput ) {
- $stashInfo->output = $parserOutput;
-
+ if ( is_object( $stashInfo ) && $stashInfo->output instanceof ParserOutput ) {
return $stashInfo;
}
}
// Store what is actually needed and split the output into another key (T204742)
- $parserOutputID = md5( $key );
$stashInfo = (object)[
'pstContent' => $pstContent,
- 'outputID' => $parserOutputID,
+ 'output' => $parserOutput,
'timestamp' => $timestamp,
'edits' => $user->getEditCount()
];
- $ok = $this->cache->set( $key, $stashInfo, $ttl );
- if ( $ok ) {
- $ok = $this->cache->set(
- $this->getStashParserOutputKey( $parserOutputID ),
- $parserOutput,
- $ttl
- );
- }
-
+ $ok = $this->cache->set( $key, $stashInfo, $ttl, BagOStuff::WRITE_ALLOW_SEGMENTS );
if ( $ok ) {
// These blobs can waste slots in low cardinality memcached slabs
$this->pruneExcessStashedEntries( $user, $key );
$keyList = $this->cache->get( $key ) ?: [];
if ( count( $keyList ) >= self::MAX_CACHE_RECENT ) {
$oldestKey = array_shift( $keyList );
- $this->cache->delete( $oldestKey );
+ $this->cache->delete( $oldestKey, BagOStuff::WRITE_PRUNE_SEGMENTS );
}
$keyList[] = $newKey;