Skip to content

Commit 67d93a6

Browse files
jenkins-botGerrit Code Review
jenkins-bot
authored and
Gerrit Code Review
committed
Merge "rdbms: track active transaction IDs for named locks and temp tables"
2 parents db559f6 + 10a27c7 commit 67d93a6

File tree

6 files changed

+97
-38
lines changed

6 files changed

+97
-38
lines changed

autoload.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,6 +1870,7 @@
18701870
'Wikimedia\\Rdbms\\Subquery' => __DIR__ . '/includes/libs/rdbms/encasing/Subquery.php',
18711871
'Wikimedia\\Rdbms\\TimestampType' => __DIR__ . '/includes/libs/rdbms/dbal/TimestampType.php',
18721872
'Wikimedia\\Rdbms\\TinyIntType' => __DIR__ . '/includes/libs/rdbms/dbal/TinyIntType.php',
1873+
'Wikimedia\\Rdbms\\TransactionIdentifier' => __DIR__ . '/includes/libs/rdbms/database/utils/TransactionIdentifier.php',
18731874
'Wikimedia\\Rdbms\\TransactionManager' => __DIR__ . '/includes/libs/rdbms/database/TransactionManager.php',
18741875
'Wikimedia\\Rdbms\\TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php',
18751876
'Wikimedia\\Reflection\\GhostFieldAccessTrait' => __DIR__ . '/includes/libs/GhostFieldAccessTrait.php',

includes/libs/rdbms/database/Database.php

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
119119
/** @var int[] Prior flags member variable values */
120120
private $priorFlags = [];
121121

122-
/** @var array<string,float> Map of (name => UNIX timestamp) for locks obtained via lock() */
122+
/** @var array<string,array> Map of (name => (UNIX time,trx ID)) for current lock() mutexes */
123123
protected $sessionNamedLocks = [];
124-
/** @var array Map of (table name => 1) for current TEMPORARY tables */
124+
/** @var array<string,array> Map of (name => (type,pristine,trx ID)) for current temp tables */
125125
protected $sessionTempTables = [];
126-
/** @var array Map of (table name => 1) for current TEMPORARY tables */
127-
protected $sessionDirtyTempTables = [];
128126

129127
/** @var array|null Replication lag estimate at the time of BEGIN for the last transaction */
130128
private $trxReplicaLagStatus = null;
@@ -1086,8 +1084,10 @@ protected function getTempTableWrites( $sql, $pseudoPermanent ) {
10861084
if ( $queryVerb === 'CREATE' ) {
10871085
// Record the type of temporary table being created
10881086
$tableType = $pseudoPermanent ? self::TEMP_PSEUDO_PERMANENT : self::TEMP_NORMAL;
1087+
} elseif ( isset( $this->sessionTempTables[$table] ) ) {
1088+
$tableType = $this->sessionTempTables[$table]['type'];
10891089
} else {
1090-
$tableType = $this->sessionTempTables[$table] ?? null;
1090+
$tableType = null;
10911091
}
10921092

10931093
if ( $tableType !== null ) {
@@ -1110,17 +1110,24 @@ protected function registerTempWrites( $ret, array $changes ) {
11101110
foreach ( $changes as list( $tmpTableType, $verb, $table ) ) {
11111111
switch ( $verb ) {
11121112
case 'CREATE':
1113-
$this->sessionTempTables[$table] = $tmpTableType;
1113+
$this->sessionTempTables[$table] = [
1114+
'type' => $tmpTableType,
1115+
'pristine' => true,
1116+
'trxId' => $this->transactionManager->getTrxId()
1117+
];
11141118
break;
11151119
case 'DROP':
11161120
unset( $this->sessionTempTables[$table] );
1117-
unset( $this->sessionDirtyTempTables[$table] );
11181121
break;
11191122
case 'TRUNCATE':
1120-
unset( $this->sessionDirtyTempTables[$table] );
1123+
if ( isset( $this->sessionTempTables[$table] ) ) {
1124+
$this->sessionTempTables[$table]['pristine'] = true;
1125+
}
11211126
break;
11221127
default:
1123-
$this->sessionDirtyTempTables[$table] = 1;
1128+
if ( isset( $this->sessionTempTables[$table] ) ) {
1129+
$this->sessionTempTables[$table]['pristine'] = false;
1130+
}
11241131
break;
11251132
}
11261133
}
@@ -1136,10 +1143,9 @@ protected function registerTempWrites( $ret, array $changes ) {
11361143
protected function isPristineTemporaryTable( $table ) {
11371144
$rawTable = $this->tableName( $table, 'raw' );
11381145

1139-
return (
1140-
isset( $this->sessionTempTables[$rawTable] ) &&
1141-
!isset( $this->sessionDirtyTempTables[$rawTable] )
1142-
);
1146+
return isset( $this->sessionTempTables[$rawTable] )
1147+
? $this->sessionTempTables[$rawTable]['pristine']
1148+
: false;
11431149
}
11441150

11451151
public function query( $sql, $fname = __METHOD__, $flags = self::QUERY_NORMAL ) {
@@ -1503,7 +1509,6 @@ private function handleSessionLossPreconnect() {
15031509
// https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
15041510
// https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
15051511
$this->sessionTempTables = [];
1506-
$this->sessionDirtyTempTables = [];
15071512
// https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
15081513
// https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
15091514
$this->sessionNamedLocks = [];
@@ -1512,7 +1517,7 @@ private function handleSessionLossPreconnect() {
15121517
// Clear additional subclass fields
15131518
$oldTrxId = $this->transactionManager->consumeTrxId();
15141519
$this->doHandleSessionLossPreconnect();
1515-
$this->transactionManager->transactionWritingOut( $this, $oldTrxId );
1520+
$this->transactionManager->transactionWritingOut( $this, (string)$oldTrxId );
15161521
}
15171522

15181523
/**
@@ -5111,22 +5116,22 @@ public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
51115116
$lockTsUnix = $this->doLock( $lockName, $method, $timeout );
51125117
if ( $lockTsUnix !== null ) {
51135118
$locked = true;
5114-
$this->sessionNamedLocks[$lockName] = $lockTsUnix;
5119+
$this->sessionNamedLocks[$lockName] = [
5120+
'ts' => $lockTsUnix,
5121+
'trxId' => $this->transactionManager->getTrxId()
5122+
];
51155123
} else {
51165124
$locked = false;
5117-
$this->queryLogger->info( __METHOD__ . " failed to acquire lock '{lockname}'",
5125+
$this->queryLogger->info(
5126+
__METHOD__ . " failed to acquire lock '{lockname}'",
51185127
[
51195128
'lockname' => $lockName,
51205129
'db_log_category' => 'locking'
5121-
] );
5130+
]
5131+
);
51225132
}
51235133

5124-
if ( $this->fieldHasBit( $flags, self::LOCK_TIMESTAMP ) ) {
5125-
// @phan-suppress-next-line PhanTypeMismatchReturnNullable Possible null is documented on the constant
5126-
return $lockTsUnix;
5127-
} else {
5128-
return $locked;
5129-
}
5134+
return $this->fieldHasBit( $flags, self::LOCK_TIMESTAMP ) ? $lockTsUnix : $locked;
51305135
}
51315136

51325137
/**

includes/libs/rdbms/database/IDatabase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2134,7 +2134,7 @@ public function lockIsFree( $lockName, $method );
21342134
* @param string $method Name of the calling method
21352135
* @param int $timeout Acquisition timeout in seconds (0 means non-blocking)
21362136
* @param int $flags Bit field of IDatabase::LOCK_* constants
2137-
* @return bool|float Success
2137+
* @return bool|float|null Success (bool); acquisition time (float/null) if LOCK_TIMESTAMP
21382138
* @throws DBError If an error occurs, {@see query}
21392139
*/
21402140
public function lock( $lockName, $method, $timeout = 5, $flags = 0 );

includes/libs/rdbms/database/TransactionManager.php

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ class TransactionManager {
5050
/** @var string Prefix to the atomic section counter used to make savepoint IDs */
5151
private const SAVEPOINT_PREFIX = 'wikimedia_rdbms_atomic';
5252

53-
/** @var string Application-side ID of the active transaction or an empty string otherwise */
54-
private $trxId = '';
53+
/** @var TransactionIdentifier|null Application-side ID of the active transaction; null if none */
54+
private $trxId;
5555
/** @var float|null UNIX timestamp at the time of BEGIN for the last transaction */
5656
private $trxTimestamp = null;
5757
/** @var int Transaction status */
@@ -115,7 +115,7 @@ public function __construct( LoggerInterface $logger = null, $profiler = null )
115115
}
116116

117117
public function trxLevel() {
118-
return ( $this->trxId != '' ) ? 1 : 0;
118+
return $this->trxId ? 1 : 0;
119119
}
120120

121121
/**
@@ -124,9 +124,7 @@ public function trxLevel() {
124124
* @param string $fname method name
125125
*/
126126
public function newTrxId( $mode, $fname ) {
127-
static $nextTrxId;
128-
$nextTrxId = ( $nextTrxId !== null ? $nextTrxId++ : mt_rand() ) % 0xffff;
129-
$this->trxId = sprintf( '%06x', mt_rand( 0, 0xffffff ) ) . sprintf( '%04x', $nextTrxId );
127+
$this->trxId = new TransactionIdentifier();
130128
$this->trxStatus = self::STATUS_TRX_OK;
131129
$this->trxStatusIgnoredCause = null;
132130
$this->trxWriteDuration = 0.0;
@@ -149,13 +147,23 @@ public function newTrxId( $mode, $fname ) {
149147
}
150148

151149
/**
152-
* Reset the application-side transaction ID and return the old one
150+
* Get the application-side transaction identifier instance
151+
*
152+
* @return TransactionIdentifier Token for the active transaction; null if there isn't one
153+
*/
154+
public function getTrxId() {
155+
return $this->trxId;
156+
}
157+
158+
/**
159+
* Reset the application-side transaction identifier instance and return the old one
160+
*
153161
* This will become private soon.
154-
* @return string The old transaction ID or an empty string if there wasn't one
162+
* @return TransactionIdentifier|null The old transaction token; null if there wasn't one
155163
*/
156164
public function consumeTrxId() {
157165
$old = $this->trxId;
158-
$this->trxId = '';
166+
$this->trxId = null;
159167
$this->trxAtomicCounter = 0;
160168

161169
return $old;
@@ -547,7 +555,7 @@ public function transactionWritingIn( $serverName, $domainId ) {
547555
$this->profiler->transactionWritingIn(
548556
$serverName,
549557
$domainId,
550-
$this->trxId
558+
(string)$this->trxId
551559
);
552560
}
553561
}
@@ -570,7 +578,7 @@ public function recordQueryCompletion( $sql, $startTime, $isPermWrite, $rowCount
570578
$startTime,
571579
$isPermWrite,
572580
$rowCount,
573-
$this->trxId,
581+
(string)$this->trxId,
574582
$serverName
575583
);
576584
}
@@ -839,7 +847,7 @@ public function onRollback( IDatabase $db ) {
839847
$this->setTrxStatusToNone();
840848
$this->resetTrxAtomicLevels();
841849
$this->clearPreEndCallbacks();
842-
$this->transactionWritingOut( $db, $oldTrxId );
850+
$this->transactionWritingOut( $db, (string)$oldTrxId );
843851
}
844852

845853
public function onCommitInCriticalSection( IDatabase $db ) {
@@ -848,7 +856,7 @@ public function onCommitInCriticalSection( IDatabase $db ) {
848856
$this->setTrxStatusToNone();
849857
if ( $this->trxDoneWrites ) {
850858
$lastWriteTime = microtime( true );
851-
$this->transactionWritingOut( $db, $oldTrxId );
859+
$this->transactionWritingOut( $db, (string)$oldTrxId );
852860
}
853861
return $lastWriteTime;
854862
}

includes/libs/rdbms/database/utils/AtomicSectionIdentifier.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
/**
2424
* Class used for token representing identifiers for atomic sections from IDatabase instances
25+
*
26+
* @ingroup Database
27+
* @internal
2528
*/
2629
class AtomicSectionIdentifier {
2730
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/**
3+
* This program is free software; you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation; either version 2 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along
14+
* with this program; if not, write to the Free Software Foundation, Inc.,
15+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
* http://www.gnu.org/copyleft/gpl.html
17+
*
18+
* @file
19+
* @ingroup Database
20+
*/
21+
namespace Wikimedia\Rdbms;
22+
23+
/**
24+
* Class used for token representing identifiers for atomic transactions from IDatabase instances
25+
*
26+
* @ingroup Database
27+
* @internal
28+
*/
29+
class TransactionIdentifier {
30+
/** @var string Application-side ID of the active transaction or an empty string otherwise */
31+
private $id = '';
32+
33+
public function __construct() {
34+
static $nextId;
35+
$nextId = ( $nextId !== null ? $nextId++ : mt_rand() ) % 0xffff;
36+
$this->id = sprintf( '%06x', mt_rand( 0, 0xffffff ) ) . sprintf( '%04x', $nextId );
37+
}
38+
39+
public function __toString() {
40+
return $this->id;
41+
}
42+
}

0 commit comments

Comments
 (0)