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