* @author Aaron Schulz
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Prepare an edit in shared cache so that it can be reused on edit
*
const ERROR_UNCACHEABLE = 'uncacheable';
public function execute() {
- global $wgMemc;
-
$user = $this->getUser();
$params = $this->extractRequestParams();
// The user will abort the AJAX request by pressing "save", so ignore that
ignore_user_abort( true );
+ // Use the master DB for fast blocking locks
+ $dbw = wfGetDB( DB_MASTER );
+
// Get a key based on the source text, format, and user preferences
$key = self::getStashKey( $title, $content, $user );
// De-duplicate requests on the same key
if ( $user->pingLimiter( 'stashedit' ) ) {
$status = 'ratelimited';
- } elseif ( $wgMemc->lock( $key, 0, 30 ) ) {
- /** @noinspection PhpUnusedLocalVariableInspection */
- $unlocker = new ScopedCallback( function() use ( $key ) {
- global $wgMemc;
- $wgMemc->unlock( $key );
- } );
+ } elseif ( $dbw->lock( $key, __METHOD__, 1 ) ) {
$status = self::parseAndStash( $page, $content, $user );
+ $dbw->unlock( $key, __METHOD__ );
} else {
$status = 'busy';
}
* @since 1.25
*/
public static function parseAndStash( WikiPage $page, Content $content, User $user ) {
- global $wgMemc;
+ $cache = ObjectCache::getLocalClusterInstance();
+ $logger = LoggerFactory::getInstance( 'StashEdit' );
$format = $content->getDefaultFormat();
$editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false );
if ( $editInfo && $editInfo->output ) {
$key = self::getStashKey( $page->getTitle(), $content, $user );
+ // Let extensions add ParserOutput metadata or warm other caches
+ Hooks::run( 'ParserOutputStashForEdit', array( $page, $content, $editInfo->output ) );
+
list( $stashInfo, $ttl ) = self::buildStashValue(
$editInfo->pstContent, $editInfo->output, $editInfo->timestamp
);
if ( $stashInfo ) {
- $ok = $wgMemc->set( $key, $stashInfo, $ttl );
+ $ok = $cache->set( $key, $stashInfo, $ttl );
if ( $ok ) {
- wfDebugLog( 'StashEdit', "Cached parser output for key '$key'." );
+
+ $logger->debug( "Cached parser output for key '$key'." );
return self::ERROR_NONE;
} else {
- wfDebugLog( 'StashEdit', "Failed to cache parser output for key '$key'." );
+ $logger->error( "Failed to cache parser output for key '$key'." );
return self::ERROR_CACHE;
}
} else {
- wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key'." );
+ $logger->info( "Uncacheable parser output for key '$key'." );
return self::ERROR_UNCACHEABLE;
}
}
* will do nothing. Provided the values are cacheable, they will be stored
* in memcached so that final edit submission might make use of them.
*
- * @param Article|WikiPage $page Page title
+ * @param Page|Article|WikiPage $page Page title
* @param Content $content Proposed page content
* @param Content $pstContent The result of preSaveTransform() on $content
* @param ParserOutput $pOut The result of getParserOutput() on $pstContent
Page $page, Content $content, Content $pstContent, ParserOutput $pOut,
ParserOptions $pstOpts, ParserOptions $pOpts, $timestamp
) {
- global $wgMemc;
+ $cache = ObjectCache::getLocalClusterInstance();
+ $logger = LoggerFactory::getInstance( 'StashEdit' );
// getIsPreview() controls parser function behavior that references things
// like user/revision that don't exists yet. The user/text should already
$canonicalPOpts->setIsPreview( true ); // force match
$canonicalPOpts->setTimestamp( $pOpts->getTimestamp() ); // force match
if ( !$pOpts->matches( $canonicalPOpts ) ) {
- wfDebugLog( 'StashEdit', "Uncacheable preview output for key '$key' (options)." );
+ $logger->info( "Uncacheable preview output for key '$key' (options)." );
return false;
}
// Build a value to cache with a proper TTL
list( $stashInfo, $ttl ) = self::buildStashValue( $pstContent, $pOut, $timestamp );
if ( !$stashInfo ) {
- wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key' (rev/TTL)." );
+ $logger->info( "Uncacheable parser output for key '$key' (rev/TTL)." );
return false;
}
- $ok = $wgMemc->set( $key, $stashInfo, $ttl );
+ $ok = $cache->set( $key, $stashInfo, $ttl );
if ( !$ok ) {
- wfDebugLog( 'StashEdit', "Failed to cache preview parser output for key '$key'." );
+ $logger->error( "Failed to cache preview parser output for key '$key'." );
} else {
- wfDebugLog( 'StashEdit', "Cached preview output for key '$key'." );
+ $logger->debug( "Cached preview output for key '$key'." );
}
return $ok;
* @return stdClass|bool Returns false on cache miss
*/
public static function checkCache( Title $title, Content $content, User $user ) {
- global $wgMemc;
+ $cache = ObjectCache::getLocalClusterInstance();
+ $logger = LoggerFactory::getInstance( 'StashEdit' );
+ $stats = RequestContext::getMain()->getStats();
$key = self::getStashKey( $title, $content, $user );
- $editInfo = $wgMemc->get( $key );
+ $editInfo = $cache->get( $key );
if ( !is_object( $editInfo ) ) {
$start = microtime( true );
// We ignore user aborts and keep parsing. Block on any prior parsing
- // so as to use it's results and make use of the time spent parsing.
- if ( $wgMemc->lock( $key, 30, 30 ) ) {
- $editInfo = $wgMemc->get( $key );
- $wgMemc->unlock( $key );
- }
- $sec = microtime( true ) - $start;
- if ( $sec > .01 ) {
- wfDebugLog( 'StashEdit', "Waited $sec seconds on '$key'." );
+ // so as to use its results and make use of the time spent parsing.
+ // Skip this logic if there no master connection in case this method
+ // is called on an HTTP GET request for some reason.
+ $lb = wfGetLB();
+ $dbw = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
+ if ( $dbw && $dbw->lock( $key, __METHOD__, 30 ) ) {
+ $editInfo = $cache->get( $key );
+ $dbw->unlock( $key, __METHOD__ );
}
+
+ $timeMs = 1000 * max( 0, microtime( true ) - $start );
+ $stats->timing( 'editstash.lock-wait-time', $timeMs );
}
if ( !is_object( $editInfo ) || !$editInfo->output ) {
- wfDebugLog( 'StashEdit', "No cache value for key '$key'." );
+ $stats->increment( 'editstash.cache-misses' );
+ $logger->debug( "No cache value for key '$key'." );
return false;
}
$time = wfTimestamp( TS_UNIX, $editInfo->output->getTimestamp() );
if ( ( time() - $time ) <= 3 ) {
- wfDebugLog( 'StashEdit', "Timestamp-based cache hit for key '$key'." );
+ $stats->increment( 'editstash.cache-hits' );
+ $logger->debug( "Timestamp-based cache hit for key '$key'." );
return $editInfo; // assume nothing changed
}
}
if ( $changed || $res->numRows() != $templateUses ) {
- wfDebugLog( 'StashEdit', "Stale cache for key '$key'; template changed." );
+ $stats->increment( 'editstash.cache-misses' );
+ $logger->info( "Stale cache for key '$key'; template changed." );
return false;
}
}
}
if ( $changed || $res->numRows() != count( $files ) ) {
- wfDebugLog( 'StashEdit', "Stale cache for key '$key'; file changed." );
+ $stats->increment( 'editstash.cache-misses' );
+ $logger->info( "Stale cache for key '$key'; file changed." );
return false;
}
}
- wfDebugLog( 'StashEdit', "Cache hit for key '$key'." );
+ $stats->increment( 'editstash.cache-hits' );
+ $logger->debug( "Cache hit for key '$key'." );
return $editInfo;
}
return true;
}
+ function isWriteMode() {
+ return true;
+ }
+
function isInternal() {
return true;
}