From 836f10dcbd7343c91fb70632efe9047e9ab528ed Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 29 Nov 2018 09:03:20 -0500 Subject: [PATCH] User: Bypass repeatable-read when creating an actor_id When MySQL is using repeatable-read transaction isolation (which is the default), the following sequence of events can occur: 1. Request A: Begin a transaction. 2. Request A: Try to select the actor ID for a user. Find no rows. 3. Request B: Insert an actor ID for that user. 4. Request A: Try to insert an actor ID for the user. Fails because one exists. 5. Request A: Try to select the actor ID that must exist. Fail because of the snapshot created at step 2. In MySQL we can avoid this issue at step #5 by using a locking select (FOR UPDATE or LOCK IN SHARE MODE), so let's do that. Bug: T210621 Change-Id: I6c1d255fdd14c6f49d2ea9790e7bd7d101e98ee4 (cherry picked from commit 37f48fdb25a78ba7623c57b50cdfd842292d3ccb) --- RELEASE-NOTES-1.31 | 1 + includes/user/User.php | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES-1.31 b/RELEASE-NOTES-1.31 index c5a2a9775b..5569367745 100644 --- a/RELEASE-NOTES-1.31 +++ b/RELEASE-NOTES-1.31 @@ -48,6 +48,7 @@ THIS IS NOT A RELEASE YET if --lang is used with the command-line installer (install.php). * Fix addition of ug_expiry column to user_groups table on MSSQL. * (T204767) Add join conditions to ActiveUsersPager +* (T210621) User: Bypass repeatable-read when creating an actor_id. == MediaWiki 1.31.1 == diff --git a/includes/user/User.php b/includes/user/User.php index aa211841f8..76691eaa1c 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -2465,8 +2465,14 @@ class User implements IDBAccessObject, UserIdentity { $this->mActorId = (int)$dbw->insertId(); } else { // Outdated cache? - list( , $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed ); - $this->mActorId = (int)$dbw->selectField( 'actor', 'actor_id', $q, __METHOD__, $options ); + // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot. + $this->mActorId = (int)$dbw->selectField( + 'actor', + 'actor_id', + $q, + __METHOD__, + [ 'LOCK IN SHARE MODE' ] + ); if ( !$this->mActorId ) { throw new CannotCreateActorException( "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}" -- 2.20.1