* @return bool
*/
public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
- global $wgEnableParserCache;
-
- return $wgEnableParserCache
- && $parserOptions->getStubThreshold() == 0
+ return $parserOptions->getStubThreshold() == 0
&& $this->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
&& $this->getContentHandler()->isParserCacheSupported();
$useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $parserOptions->getStubThreshold() ) {
- wfIncrStats( 'pcache_miss_stub' );
+ wfIncrStats( 'pcache.miss.stub' );
}
if ( $useParserCache ) {
$conditions['page_latest'] = $lastRevision;
}
- $now = wfTimestampNow();
$row = array( /* SET */
'page_latest' => $revision->getId(),
- 'page_touched' => $dbw->timestamp( $now ),
+ 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
'page_is_redirect' => $rt !== null ? 1 : 0,
'page_len' => $len,
* revision: The revision object for the inserted revision, or null.
*
* @since 1.21
+ * @throws MWException
*/
public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
User $user = null, $serialFormat = null
if ( $changed ) {
$dbw->begin( __METHOD__ );
- try {
- $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
- $status->merge( $prepStatus );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
+ $status->merge( $prepStatus );
- if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
- return $status;
- }
- $revisionId = $revision->insertOn( $dbw );
+ return $status;
+ }
+ $revisionId = $revision->insertOn( $dbw );
- // Update page
- //
- // We check for conflicts by comparing $oldid with the current latest revision ID.
- $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
+ // Update page
+ //
+ // We check for conflicts by comparing $oldid with the current latest revision ID.
+ $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
- if ( !$ok ) {
- // Belated edit conflict! Run away!!
- $status->fatal( 'edit-conflict' );
+ if ( !$ok ) {
+ // Belated edit conflict! Run away!!
+ $status->fatal( 'edit-conflict' );
- $dbw->rollback( __METHOD__ );
+ $dbw->rollback( __METHOD__ );
- return $status;
- }
+ return $status;
+ }
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- // Update recentchanges
- if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- // Mark as patrolled if the user can do so
- $patrolled = $wgUseRCPatrol && !count(
- $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- // Add RC row to the DB
- $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
- $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
- $revisionId, $patrolled
- );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- // Log auto-patrolled edits
- if ( $patrolled ) {
- PatrolLog::record( $rc, true, $user );
- }
- }
- $user->incEditCount();
- } catch ( Exception $e ) {
- $dbw->rollback( __METHOD__ );
- // Question: Would it perhaps be better if this method turned all
- // exceptions into $status's?
- throw $e;
+ // Update recentchanges
+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+ // Mark as patrolled if the user can do so
+ $patrolled = $wgUseRCPatrol && !count(
+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+ // Add RC row to the DB
+ RecentChange::notifyEdit(
+ $now, $this->mTitle, $isminor, $user, $summary,
+ $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
+ $revisionId, $patrolled
+ );
}
+
+ $user->incEditCount();
+
$dbw->commit( __METHOD__ );
} else {
// Bug 32948: revision ID must be set to page {{REVISIONID}} and
$revision = null;
// Update page_touched, this is usually implicit in the page update
// Other cache updates are done in onArticleEdit()
- $this->mTitle->invalidateCache();
+ $this->mTitle->invalidateCache( $now );
}
} else {
// Create new article
$status->value['new'] = true;
$dbw->begin( __METHOD__ );
- try {
- $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
- $status->merge( $prepStatus );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
+ $status->merge( $prepStatus );
- if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
+ if ( !$status->isOK() ) {
+ $dbw->rollback( __METHOD__ );
- return $status;
- }
+ return $status;
+ }
- $status->merge( $prepStatus );
+ $status->merge( $prepStatus );
- // Add the page record; stake our claim on this title!
- // This will return false if the article already exists
- $newid = $this->insertOn( $dbw );
+ // Add the page record; stake our claim on this title!
+ // This will return false if the article already exists
+ $newid = $this->insertOn( $dbw );
- if ( $newid === false ) {
- $dbw->rollback( __METHOD__ );
- $status->fatal( 'edit-already-exists' );
+ if ( $newid === false ) {
+ $dbw->rollback( __METHOD__ );
+ $status->fatal( 'edit-already-exists' );
- return $status;
- }
+ return $status;
+ }
- // Save the revision text...
- $revision = new Revision( array(
- 'page' => $newid,
- 'title' => $this->getTitle(), // for determining the default content model
- 'comment' => $summary,
- 'minor_edit' => $isminor,
- 'text' => $serialized,
- 'len' => $newsize,
- 'user' => $user->getId(),
- 'user_text' => $user->getName(),
- 'timestamp' => $now,
- 'content_model' => $content->getModel(),
- 'content_format' => $serialFormat,
- ) );
- $revisionId = $revision->insertOn( $dbw );
+ // Save the revision text...
+ $revision = new Revision( array(
+ 'page' => $newid,
+ 'title' => $this->getTitle(), // for determining the default content model
+ 'comment' => $summary,
+ 'minor_edit' => $isminor,
+ 'text' => $serialized,
+ 'len' => $newsize,
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $now,
+ 'content_model' => $content->getModel(),
+ 'content_format' => $serialFormat,
+ ) );
+ $revisionId = $revision->insertOn( $dbw );
- // Bug 37225: use accessor to get the text as Revision may trim it
- $content = $revision->getContent(); // sanity; get normalized version
+ // Bug 37225: use accessor to get the text as Revision may trim it
+ $content = $revision->getContent(); // sanity; get normalized version
- if ( $content ) {
- $newsize = $content->getSize();
- }
+ if ( $content ) {
+ $newsize = $content->getSize();
+ }
- // Update the page record with revision data
- $this->updateRevisionOn( $dbw, $revision, 0 );
+ // Update the page record with revision data
+ $this->updateRevisionOn( $dbw, $revision, 0 );
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- // Update recentchanges
- if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- // Mark as patrolled if the user can do so
- $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
- $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
- // Add RC row to the DB
- $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
- '', $newsize, $revisionId, $patrolled );
+ // Update recentchanges
+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+ // Mark as patrolled if the user can do so
+ $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+ // Add RC row to the DB
+ RecentChange::notifyNew(
+ $now, $this->mTitle, $isminor, $user, $summary, $bot,
+ '', $newsize, $revisionId, $patrolled
+ );
+ }
- // Log auto-patrolled edits
- if ( $patrolled ) {
- PatrolLog::record( $rc, true, $user );
- }
- }
- $user->incEditCount();
+ $user->incEditCount();
- } catch ( Exception $e ) {
- $dbw->rollback( __METHOD__ );
- throw $e;
- }
$dbw->commit( __METHOD__ );
// Update links, etc.
$status->value['revision'] = $revision;
$hook_args = array( &$this, &$user, $content, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
Hooks::run( 'PageContentSaveComplete', $hook_args );
// Promote user to any groups they meet the criteria for
- $dbw->onTransactionIdle( function () use ( $user ) {
+ DeferredUpdates::addCallableUpdate( function () use ( $user ) {
$user->addAutopromoteOnceGroups( 'onEdit' );
$user->addAutopromoteOnceGroups( 'onView' ); // b/c
} );
* - 'no-change': don't update the article count, ever
*/
public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
- global $wgEnableParserCache;
-
$options += array(
'changed' => true,
'created' => false,
$editInfo = $this->mPreparedEdit;
}
- // Save it to the parser cache
- if ( $wgEnableParserCache ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save(
- $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid
- );
- }
+ // Save it to the parser cache.
+ // Make sure the cache time matches page_touched to avoid double parsing.
+ ParserCache::singleton()->save(
+ $editInfo->output, $this, $editInfo->popts,
+ $revision->getTimestamp(), $editInfo->revid
+ );
// Update the links tables and other secondary data
if ( $content ) {
$recursive = $options['changed']; // bug 50785
$updates = $content->getSecondaryDataUpdates(
$this->getTitle(), null, $recursive, $editInfo->output );
- DataUpdate::runUpdates( $updates );
+ foreach ( $updates as $update ) {
+ DeferredUpdates::addUpdate( $update );
+ }
}
Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- JobQueueGroup::singleton()->push( array(
- // Flush old entries from the `recentchanges` table
- RecentChangesUpdateJob::newPurgeJob(),
- // Update the cached list of active users
- RecentChangesUpdateJob::newCacheUpdateJob()
- ) );
+ // Flush old entries from the `recentchanges` table
+ if ( mt_rand( 0, 9 ) == 0 ) {
+ JobQueueGroup::singleton()->lazyPush( RecentChangesUpdateJob::newPurgeJob() );
+ }
}
if ( !$this->exists() ) {
} elseif ( $options['changed'] ) { // bug 50785
self::onArticleEdit( $this->mTitle );
}
-
}
/**
$dbw->begin( __METHOD__ );
if ( $id == 0 ) {
- $this->loadPageData( 'forupdate' );
+ // T98706: lock the page from various other updates but avoid using
+ // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
+ // the revisions queries (which also JOIN on user). Only lock the page
+ // row and CAS check on page_latest to see if the trx snapshot matches.
+ $latest = $this->lock();
+
+ $this->loadPageData( WikiPage::READ_LATEST );
$id = $this->getID();
- if ( $id == 0 ) {
+ if ( $id == 0 || $this->getLatest() != $latest ) {
+ // Page not there or trx snapshot is stale
$dbw->rollback( __METHOD__ );
$status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
return $status;
}
+ /**
+ * Lock the page row for this title and return page_latest (or 0)
+ *
+ * @return integer
+ */
+ protected function lock() {
+ return (int)wfGetDB( DB_MASTER )->selectField(
+ 'page',
+ 'page_latest',
+ array(
+ 'page_namespace' => $this->getTitle()->getNamespace(),
+ 'page_title' => $this->getTitle()->getDBkey()
+ ),
+ __METHOD__,
+ array( 'FOR UPDATE' )
+ );
+ }
+
/**
* Do some database updates after deletion
*
// Update existence markers on article/talk tabs...
$other = $title->getOtherPage();
- $other->invalidateCache();
$other->purgeSquid();
$title->touchLinks();
// Update existence markers on article/talk tabs...
$other = $title->getOtherPage();
- $other->invalidateCache();
$other->purgeSquid();
$title->touchLinks();
$params['isOpportunistic'] = true;
$params['rootJobTimestamp'] = $parserOutput->getCacheTime();
- JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs(
+ JobQueueGroup::singleton()->lazyPush( EnqueueJob::newFromLocalJobs(
new JobSpecification( 'refreshLinks', $params,
array( 'removeDuplicates' => true ), $this->mTitle )
) );
- return;
}
-
- return;
}
/**