Completed
Branch master (098997)
by
unknown
28:44
created

MySqlLockManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * MySQL version of DBLockManager that supports shared locks.
4
 *
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!
8
 *
9
 * All lock servers must have the innodb table defined in maintenance/locking/filelocks.sql.
10
 * All locks are non-blocking, which avoids deadlocks.
11
 *
12
 * @ingroup LockManager
13
 */
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
20
	];
21
22
	public function __construct( array $config ) {
23
		parent::__construct( $config );
24
25
		$this->session = substr( $this->session, 0, 31 ); // fit to field
26
	}
27
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__ );
33
	}
34
35
	/**
36
	 * Get a connection to a lock DB and acquire locks on $paths.
37
	 * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
38
	 *
39
	 * @see DBLockManager::getLocksOnServer()
40
	 * @param string $lockSrv
41
	 * @param array $paths
42
	 * @param string $type
43
	 * @return StatusValue
44
	 */
45
	protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
46
		$status = StatusValue::newGood();
47
48
		$db = $this->getConnection( $lockSrv ); // checked in isServerUp()
49
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
60
			}
61
		}
62
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__
74
				);
75
			} else {
76
				$blocked = false;
77
			}
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__
90
			);
91
			if ( !$blocked ) {
92
				# Build up values for INSERT clause
93
				$data = [];
94
				foreach ( $keys as $key ) {
95
					$data[] = [ 'fle_key' => $key ];
96
				}
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__
105
				);
106
			}
107
		}
108
109
		if ( $blocked ) {
110
			foreach ( $paths as $path ) {
111
				$status->fatal( 'lockmanager-fail-acquirelock', $path );
112
			}
113
		}
114
115
		return $status;
116
	}
117
118
	/**
119
	 * @see QuorumLockManager::releaseAllLocks()
120
	 * @return StatusValue
121
	 */
122 View Code Duplication
	protected function releaseAllLocks() {
123
		$status = StatusValue::newGood();
124
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 );
131
				}
132
			}
133
		}
134
135
		return $status;
136
	}
137
}
138