* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @author Aaron Schulz
*/
use MediaWiki\Logger\LoggerFactory;
const PRESUME_FRESH_TTL_SEC = 30;
const MAX_CACHE_TTL = 300; // 5 minutes
+ const MAX_SIGNATURE_TTL = 60;
public function execute() {
$user = $this->getUser();
if ( strlen( $params['stashedtexthash'] ) ) {
// Load from cache since the client indicates the text is the same as last stash
$textHash = $params['stashedtexthash'];
+ if ( !preg_match( '/^[0-9a-f]{40}$/', $textHash ) ) {
+ $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
+ }
$textKey = $cache->makeKey( 'stashedit', 'text', $textHash );
$text = $cache->get( $textKey );
if ( !is_string( $text ) ) {
$title = $page->getTitle();
$key = self::getStashKey( $title, self::getContentHash( $content ), $user );
- // Use the master DB for fast blocking locks
+ // Use the master DB to allow for fast blocking locks on the "save path" where this
+ // value might actually be used to complete a page edit. If the edit submission request
+ // happens before this edit stash requests finishes, then the submission will block until
+ // 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 = wfGetDB( DB_MASTER );
- if ( !$dbw->lock( $key, __METHOD__, 1 ) ) {
+ if ( !$dbw->lock( $key, __METHOD__, 0 ) ) {
// De-duplicate requests on the same key
return self::ERROR_BUSY;
}
Hooks::run( 'ParserOutputStashForEdit',
[ $page, $content, $editInfo->output, $summary, $user ] );
+ $titleStr = (string)$title;
if ( $alreadyCached ) {
- $logger->debug( "Already cached parser output for key '$key' ('$title')." );
+ $logger->debug( "Already cached parser output for key '{cachekey}' ('{title}').",
+ [ 'cachekey' => $key, 'title' => $titleStr ] );
return self::ERROR_NONE;
}
if ( $stashInfo ) {
$ok = $cache->set( $key, $stashInfo, $ttl );
if ( $ok ) {
- $logger->debug( "Cached parser output for key '$key' ('$title')." );
+ $logger->debug( "Cached parser output for key '{cachekey}' ('{title}').",
+ [ 'cachekey' => $key, 'title' => $titleStr ] );
return self::ERROR_NONE;
} else {
- $logger->error( "Failed to cache parser output for key '$key' ('$title')." );
+ $logger->error( "Failed to cache parser output for key '{cachekey}' ('{title}').",
+ [ 'cachekey' => $key, 'title' => $titleStr ] );
return self::ERROR_CACHE;
}
} else {
- $logger->info( "Uncacheable parser output for key '$key' ('$title') [$code]." );
+ $logger->info( "Uncacheable parser output for key '{cachekey}' ('{title}') [{code}].",
+ [ 'cachekey' => $key, 'title' => $titleStr, 'code' => $code ] );
return self::ERROR_UNCACHEABLE;
}
}
* @return string|null TS_MW timestamp or null
*/
private static function lastEditTime( User $user ) {
- $time = wfGetDB( DB_REPLICA )->selectField(
- 'recentchanges',
+ $db = wfGetDB( DB_REPLICA );
+ $actorQuery = ActorMigration::newMigration()->getWhere( $db, 'rc_user', $user, false );
+ $time = $db->selectField(
+ [ 'recentchanges' ] + $actorQuery['tables'],
'MAX(rc_timestamp)',
- [ 'rc_user_text' => $user->getName() ],
- __METHOD__
+ [ $actorQuery['conds'] ],
+ __METHOD__,
+ [],
+ $actorQuery['joins']
);
return wfTimestampOrNull( TS_MW, $time );
// Put an upper limit on the TTL for sanity to avoid extreme template/file staleness.
$since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
$ttl = min( $parserOutput->getCacheExpiry() - $since, self::MAX_CACHE_TTL );
+
+ // Avoid extremely stale user signature timestamps (T84843)
+ if ( $parserOutput->getFlag( 'user-signature' ) ) {
+ $ttl = min( $ttl, self::MAX_SIGNATURE_TTL );
+ }
+
if ( $ttl <= 0 ) {
return [ null, 0, 'no_ttl' ];
}