Merge "Add logic for "tags" in ApiBase"
[lhc/web/wiklou.git] / includes / api / ApiStashEdit.php
index ebddd51..00675f4 100644 (file)
@@ -19,6 +19,8 @@
  * @author Aaron Schulz
  */
 
+use MediaWiki\Logger\LoggerFactory;
+
 /**
  * Prepare an edit in shared cache so that it can be reused on edit
  *
@@ -39,8 +41,6 @@ class ApiStashEdit extends ApiBase {
        const ERROR_UNCACHEABLE = 'uncacheable';
 
        public function execute() {
-               $cache = ObjectCache::getLocalClusterInstance();
-
                $user = $this->getUser();
                $params = $this->extractRequestParams();
 
@@ -106,17 +106,17 @@ class ApiStashEdit extends ApiBase {
                // 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 ( $cache->lock( $key, 0, 30 ) ) {
-                       /** @noinspection PhpUnusedLocalVariableInspection */
-                       $unlocker = new ScopedCallback( function() use ( $cache, $key ) {
-                               $cache->unlock( $key );
-                       } );
+               } elseif ( $dbw->lock( $key, __METHOD__, 1 ) ) {
                        $status = self::parseAndStash( $page, $content, $user );
+                       $dbw->unlock( $key, __METHOD__ );
                } else {
                        $status = 'busy';
                }
@@ -133,6 +133,7 @@ class ApiStashEdit extends ApiBase {
         */
        public static function parseAndStash( WikiPage $page, Content $content, User $user ) {
                $cache = ObjectCache::getLocalClusterInstance();
+               $logger = LoggerFactory::getInstance( 'StashEdit' );
 
                $format = $content->getDefaultFormat();
                $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false );
@@ -140,6 +141,9 @@ class ApiStashEdit extends ApiBase {
                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
                        );
@@ -147,14 +151,15 @@ class ApiStashEdit extends ApiBase {
                        if ( $stashInfo ) {
                                $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;
                        }
                }
@@ -186,6 +191,7 @@ class ApiStashEdit extends ApiBase {
                ParserOptions $pstOpts, ParserOptions $pOpts, $timestamp
        ) {
                $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
@@ -207,22 +213,22 @@ class ApiStashEdit extends ApiBase {
                $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 = $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;
@@ -247,31 +253,38 @@ class ApiStashEdit extends ApiBase {
         */
        public static function checkCache( Title $title, Content $content, User $user ) {
                $cache = ObjectCache::getLocalClusterInstance();
+               $logger = LoggerFactory::getInstance( 'StashEdit' );
+               $stats = RequestContext::getMain()->getStats();
 
                $key = self::getStashKey( $title, $content, $user );
                $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 ( $cache->lock( $key, 30, 30 ) ) {
+                       // 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 );
-                               $cache->unlock( $key );
-                       }
-                       $sec = microtime( true ) - $start;
-                       if ( $sec > .01 ) {
-                               wfDebugLog( 'StashEdit', "Waited $sec seconds on '$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
                }
 
@@ -299,7 +312,8 @@ class ApiStashEdit extends ApiBase {
                        }
 
                        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;
                        }
                }
@@ -322,12 +336,14 @@ class ApiStashEdit extends ApiBase {
                        }
 
                        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;
        }
@@ -425,6 +441,10 @@ class ApiStashEdit extends ApiBase {
                return true;
        }
 
+       function isWriteMode() {
+               return true;
+       }
+
        function isInternal() {
                return true;
        }