* @ingroup JobQueue
*/
use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\DBReplicationWaitError;
/**
* Job to update link tables for pages
// Multiple pages per job make matches unlikely
!( isset( $params['pages'] ) && count( $params['pages'] ) != 1 )
);
+ $this->params += [ 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
}
/**
// From then on, we know that any template changes at the time the base job was
// enqueued will be reflected in backlink page parses when the leaf jobs run.
if ( !isset( $this->params['range'] ) ) {
- try {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lbFactory->waitForReplication( [
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ if ( !$lbFactory->waitForReplication( [
'wiki' => wfWikiID(),
'timeout' => self::LAG_WAIT_TIMEOUT
- ] );
- } catch ( DBReplicationWaitError $e ) { // only try so hard
+ ] ) ) { // only try so hard
$stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
$stats->increment( 'refreshlinks.lag_wait_failed' );
}
// Carry over information for de-duplication
$extraParams = $this->getRootJobParams();
$extraParams['triggeredRecursive'] = true;
+ // Carry over cause information for logging
+ $extraParams['causeAction'] = $this->params['causeAction'];
+ $extraParams['causeAgent'] = $this->params['causeAgent'];
// Convert this into no more than $wgUpdateRowsPerJob RefreshLinks per-title
// jobs and possibly a recursive RefreshLinks job for the rest of the backlinks
$jobs = BacklinkJobUtils::partitionBacklinkJob(
$dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
/** @noinspection PhpUnusedLocalVariableInspection */
$scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' );
+ if ( $scopedLock === null ) {
+ // Another job is already updating the page, likely for an older revision (T170596).
+ $this->setLastError( 'LinksUpdate already running for this page, try again later.' );
+ return false;
+ }
// Get the latest ID *after* acquirePageLock() flushed the transaction.
// This is used to detect edits/moves after loadPageData() but before the scope lock.
// The works around the chicken/egg problem of determining the scope lock key.
$stats->increment( 'refreshlinks.parser_uncached' );
}
- $updates = $content->getSecondaryDataUpdates(
- $title,
- null,
- !empty( $this->params['useRecursiveLinksUpdate'] ),
- $parserOutput
- );
-
- // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
- // any pending writes they made get flushed before the doUpdate() calls below.
- // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
- $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
-
- foreach ( $updates as $update ) {
- // FIXME: This code probably shouldn't be here?
- // Needed by things like Echo notifications which need
- // to know which user caused the links update
- if ( $update instanceof LinksUpdate ) {
- $update->setRevision( $revision );
- if ( !empty( $this->params['triggeringUser'] ) ) {
- $userInfo = $this->params['triggeringUser'];
- if ( $userInfo['userId'] ) {
- $user = User::newFromId( $userInfo['userId'] );
- } else {
- // Anonymous, use the username
- $user = User::newFromName( $userInfo['userName'], false );
- }
- $update->setTriggeringUser( $user );
- }
+ $options = [
+ 'recursive' => !empty( $this->params['useRecursiveLinksUpdate'] ),
+ // Carry over cause so the update can do extra logging
+ 'causeAction' => $this->params['causeAction'],
+ 'causeAgent' => $this->params['causeAgent'],
+ 'defer' => false,
+ 'transactionTicket' => $ticket,
+ ];
+ if ( !empty( $this->params['triggeringUser'] ) ) {
+ $userInfo = $this->params['triggeringUser'];
+ if ( $userInfo['userId'] ) {
+ $options['triggeringUser'] = User::newFromId( $userInfo['userId'] );
+ } else {
+ // Anonymous, use the username
+ $options['triggeringUser'] = User::newFromName( $userInfo['userName'], false );
}
}
-
- foreach ( $updates as $update ) {
- $update->setTransactionTicket( $ticket );
- $update->doUpdate();
- }
+ $page->doSecondaryDataUpdates( $options );
InfoAction::invalidateCache( $title );
public function getDeduplicationInfo() {
$info = parent::getDeduplicationInfo();
+ unset( $info['causeAction'] );
+ unset( $info['causeAgent'] );
if ( is_array( $info['params'] ) ) {
// For per-pages jobs, the job title is that of the template that changed
// (or similar), so remove that since it ruins duplicate detection
- if ( isset( $info['pages'] ) ) {
+ if ( isset( $info['params']['pages'] ) ) {
unset( $info['namespace'] );
unset( $info['title'] );
}