jenkins-bot has submitted this change and it was merged.
Change subject: Assorted transaction logic cleanups
......................................................................
Assorted transaction logic cleanups
* Replace DB begin()/commit() with startAtomic()/endAtomic() as needed.
* Avoid using rollback(), which generates warnings, and instead doing a
SELECT FOR UPDATE and checking if the user exists instead of counting
the affected rows of the subsequent UPDATE.
* Block rename jobs until the original transaction commits and abort
jobs if the log row was not found. That row acts as a good proxy for
whether the transaction committed or not. This is better than simply
moving the job insertion to post-commit because that breaks if the job
queue is down but not the DB.
* The User::invalidateCache() already defers to pre-commit with HOLDOFF_TTL
in clearSharedCache(), so having two of the calls was redundant. The later
call was removed.
Bug: T120791
Change-Id: I33f314fb93228b090c1b82c55b1621764e072028
---
M RenameUserJob.php
M RenameuserSQL.php
2 files changed, 84 insertions(+), 33 deletions(-)
Approvals:
Legoktm: Looks good to me, approved
jenkins-bot: Verified
diff --git a/RenameUserJob.php b/RenameUserJob.php
index e483733..bfe6d4a 100644
--- a/RenameUserJob.php
+++ b/RenameUserJob.php
@@ -9,6 +9,7 @@
* - oldname : The old user name
* - newname : The new user name
* - count : The expected number of rows to update in this batch
+ * - logId : The ID of the logging table row expected to exist if the
rename was committed
*
* Additionally, one of the following groups of parameters must be set:
* a) The timestamp based rename paramaters:
@@ -55,8 +56,29 @@
}
$uniqueKey = isset( $this->params['uniqueKey'] ) ?
$this->params['uniqueKey'] : null;
$keyId = isset( $this->params['keyId'] ) ?
$this->params['keyId'] : null;
+ $logId = isset( $this->params['logId'] ) ?
$this->params['logId'] : null;
$dbw = wfGetDB( DB_MASTER );
+ if ( $logId ) {
+ # Block until the transaction that inserted this job
commits.
+ # The atomic section is for sanity as FOR UPDATE does
not lock in auto-commit mode
+ # per
http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html.
+ $dbw->startAtomic( __METHOD__ );
+ $committed = $dbw->selectField( 'logging',
+ '1',
+ array( 'log_id' => $logId ),
+ __METHOD__,
+ array( 'FOR UPDATE' )
+ );
+ $dbw->endAtomic( __METHOD__ );
+ # If the transaction inserting this job was rolled
back, detect that
+ if ( $committed === false ) { // rollback happened?
+ throw new LogicException( "Cannot run job if
the account rename failed." );
+ }
+ }
+
+ # Flush any state snapshot data (and release the lock above)
+ $dbw->commit( __METHOD__, 'flush' );
# Conditions like "*_user_text = 'x'
$conds = array( $column => $oldname );
diff --git a/RenameuserSQL.php b/RenameuserSQL.php
index 9fbacda..57dad4e 100755
--- a/RenameuserSQL.php
+++ b/RenameuserSQL.php
@@ -165,12 +165,19 @@
$contribs = User::newFromId( $this->uid )->getEditCount();
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
+ $dbw->startAtomic( __METHOD__ );
+
Hooks::run( 'RenameUserPreRename', array( $this->uid,
$this->old, $this->new ) );
- // Rename and touch the user before re-attributing edits,
- // this avoids users still being logged in and making new edits
while
- // being renamed, which leaves edits at the old name.
+ // Make sure the user exists if needed
+ if ( $this->checkIfUserExists && !self::lockUserAndGetId(
$this->old ) ) {
+ $this->debug( "User {$this->old} does not exist,
bailing out" );
+
+ return false;
+ }
+
+ // Rename and touch the user before re-attributing edits to
avoid users still being
+ // logged in and making new edits (under the old name) while
being renamed.
$this->debug( "Starting rename of {$this->old} to {$this->new}"
);
$dbw->update( 'user',
array( 'user_name' => $this->new, 'user_touched' =>
$dbw->timestamp() ),
@@ -178,27 +185,21 @@
__METHOD__
);
- if ( !$dbw->affectedRows() && $this->checkIfUserExists ) {
- $dbw->rollback();
- $this->debug( "User {$this->old} does not exist,
bailing out" );
-
- return false;
- }
-
// Reset token to break login with central auth systems.
// Again, avoids user being logged in with old name.
$user = User::newFromId( $this->uid );
$authUser = $wgAuth->getUserInstance( $user );
$authUser->resetAuthToken();
- // Delete from memcached.
+ // Purge user cache
$user->invalidateCache();
// Update ipblock list if this user has a block in there.
$dbw->update( 'ipblocks',
array( 'ipb_address' => $this->new ),
array( 'ipb_user' => $this->uid, 'ipb_address' =>
$this->old ),
- __METHOD__ );
+ __METHOD__
+ );
// Update this users block/rights log. Ideally, the logs would
be historical,
// but it is really annoying when users have "clean" block logs
by virtue of
// being renamed, which makes admin tasks more of a pain...
@@ -216,8 +217,10 @@
array( 'log_type' => $logTypesOnUser,
'log_namespace' => NS_USER,
'log_title' => $oldTitle->getDBkey() ),
- __METHOD__ );
- // Do immediate updates!
+ __METHOD__
+ );
+
+ // Do immediate re-attribution table updates...
foreach ( $this->tables as $table => $fieldSet ) {
list( $nameCol, $userCol ) = $fieldSet;
$dbw->update( $table,
@@ -227,6 +230,7 @@
);
}
+ /** @var RenameUserJob[] $jobs */
$jobs = array(); // jobs for all tables
// Construct jobqueue updates...
// FIXME: if a bureaucrat renames a user in error, he/she
@@ -294,23 +298,6 @@
$dbw->freeResult( $res );
}
- $count = count( $jobs );
- if ( $count > 0 ) {
- JobQueueGroup::singleton()->push( $jobs,
JobQueue::QOS_ATOMIC );
- $this->debug( "Queued $count jobs for {$this->old} to
{$this->new}" );
- }
-
- // Commit the transaction
- $dbw->commit();
-
- // Delete from memcached again to make sure
- $user->invalidateCache();
-
- // Clear caches and inform authentication plugins
- $user = User::newFromId( $this->uid );
- $wgAuth->updateExternalDB( $user );
- Hooks::run( 'RenameUserComplete', array( $this->uid,
$this->old, $this->new ) );
-
// Log it!
$logEntry = new ManualLogEntry( 'renameuser', 'renameuser' );
$logEntry->setPerformer( $this->renamer );
@@ -322,10 +309,52 @@
'6::edits' => $contribs
) );
$logid = $logEntry->insert();
- $logEntry->publish( $logid );
+ // Include the log_id in the jobs as a DB commit marker
+ foreach ( $jobs as $job ) {
+ $job->params['logId'] = $logid;
+ }
+
+ // Insert any jobs as needed. If this fails, then an exception
will be thrown and the
+ // DB transaction will be rolled back. If it succeeds but the
DB commit fails, then the
+ // jobs will see that the transaction was not committed and
will cancel themselves.
+ $count = count( $jobs );
+ if ( $count > 0 ) {
+ JobQueueGroup::singleton()->push( $jobs,
JobQueue::QOS_ATOMIC );
+ $this->debug( "Queued $count jobs for {$this->old} to
{$this->new}" );
+ }
+
+ // Commit the transaction
+ $dbw->endAtomic( __METHOD__ );
+
+ $that = $this;
+ $dbw->onTransactionIdle( function() use ( $that, $dbw,
$logEntry, $logid ) {
+ global $wgAuth;
+ // Keep any updates here in a transaction
+ $dbw->setFlag( DBO_TRX );
+ // Clear caches and inform authentication plugins
+ $user = User::newFromId( $that->uid );
+ $wgAuth->updateExternalDB( $user );
+ Hooks::run( 'RenameUserComplete', array( $this->uid,
$this->old, $this->new ) );
+ // Publish to RC
+ $logEntry->publish( $logid );
+ } );
$this->debug( "Finished rename for {$this->old} to
{$this->new}" );
return true;
}
+
+ /**
+ * @param string $name Current wiki local user name
+ * @return integer Returns 0 if no row was found
+ */
+ private static function lockUserAndGetId( $name ) {
+ return (int)wfGetDB( DB_MASTER )->selectField(
+ 'user',
+ 'user_id',
+ array( 'user_name' => $name ),
+ __METHOD__,
+ array( 'FOR UPDATE' )
+ );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/257746
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I33f314fb93228b090c1b82c55b1621764e072028
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Renameuser
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits