Merge "Special:Upload should not crash on failing previews"
[mediawiki.git] / includes / filebackend / lockmanager / MySqlLockManager.php
blob5936e7d1d2a8846f84c48569f59424f55982e6fa
1 <?php
2 /**
3 * MySQL version of DBLockManager that supports shared locks.
5 * Do NOT use this on connection handles that are also being used for anything
6 * else as the transaction isolation will be wrong and all the other changes will
7 * get rolled back when the locks release!
9 * All lock servers must have the innodb table defined in maintenance/locking/filelocks.sql.
10 * All locks are non-blocking, which avoids deadlocks.
12 * @ingroup LockManager
14 class MySqlLockManager extends DBLockManager {
15 /** @var array Mapping of lock types to the type actually used */
16 protected $lockTypeMap = [
17 self::LOCK_SH => self::LOCK_SH,
18 self::LOCK_UW => self::LOCK_SH,
19 self::LOCK_EX => self::LOCK_EX
22 public function __construct( array $config ) {
23 parent::__construct( $config );
25 $this->session = substr( $this->session, 0, 31 ); // fit to field
28 protected function initConnection( $lockDb, IDatabase $db ) {
29 # Let this transaction see lock rows from other transactions
30 $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
31 # Do everything in a transaction as it all gets rolled back eventually
32 $db->startAtomic( __CLASS__ );
35 /**
36 * Get a connection to a lock DB and acquire locks on $paths.
37 * This does not use GET_LOCK() per https://bugs.mysql.com/bug.php?id=1118.
39 * @see DBLockManager::getLocksOnServer()
40 * @param string $lockSrv
41 * @param array $paths
42 * @param string $type
43 * @return StatusValue
45 protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
46 $status = StatusValue::newGood();
48 $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
50 $keys = []; // list of hash keys for the paths
51 $data = []; // list of rows to insert
52 $checkEXKeys = []; // list of hash keys that this has no EX lock on
53 # Build up values for INSERT clause
54 foreach ( $paths as $path ) {
55 $key = $this->sha1Base36Absolute( $path );
56 $keys[] = $key;
57 $data[] = [ 'fls_key' => $key, 'fls_session' => $this->session ];
58 if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
59 $checkEXKeys[] = $key; // this has no EX lock on $key itself
63 # Block new writers (both EX and SH locks leave entries here)...
64 $db->insert( 'filelocks_shared', $data, __METHOD__, [ 'IGNORE' ] );
65 # Actually do the locking queries...
66 if ( $type == self::LOCK_SH ) { // reader locks
67 # Bail if there are any existing writers...
68 if ( count( $checkEXKeys ) ) {
69 $blocked = $db->selectField(
70 'filelocks_exclusive',
71 '1',
72 [ 'fle_key' => $checkEXKeys ],
73 __METHOD__
75 } else {
76 $blocked = false;
78 # Other prospective writers that haven't yet updated filelocks_exclusive
79 # will recheck filelocks_shared after doing so and bail due to this entry.
80 } else { // writer locks
81 $encSession = $db->addQuotes( $this->session );
82 # Bail if there are any existing writers...
83 # This may detect readers, but the safe check for them is below.
84 # Note: if two writers come at the same time, both bail :)
85 $blocked = $db->selectField(
86 'filelocks_shared',
87 '1',
88 [ 'fls_key' => $keys, "fls_session != $encSession" ],
89 __METHOD__
91 if ( !$blocked ) {
92 # Build up values for INSERT clause
93 $data = [];
94 foreach ( $keys as $key ) {
95 $data[] = [ 'fle_key' => $key ];
97 # Block new readers/writers...
98 $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
99 # Bail if there are any existing readers...
100 $blocked = $db->selectField(
101 'filelocks_shared',
102 '1',
103 [ 'fls_key' => $keys, "fls_session != $encSession" ],
104 __METHOD__
109 if ( $blocked ) {
110 foreach ( $paths as $path ) {
111 $status->fatal( 'lockmanager-fail-acquirelock', $path );
115 return $status;
119 * @see QuorumLockManager::releaseAllLocks()
120 * @return StatusValue
122 protected function releaseAllLocks() {
123 $status = StatusValue::newGood();
125 foreach ( $this->conns as $lockDb => $db ) {
126 if ( $db->trxLevel() ) { // in transaction
127 try {
128 $db->rollback( __METHOD__ ); // finish transaction and kill any rows
129 } catch ( DBError $e ) {
130 $status->fatal( 'lockmanager-fail-db-release', $lockDb );
135 return $status;