Index: trunk/phase3/includes/filerepo/backend/lockmanager/DBLockManager.php |
— | — | @@ -27,8 +27,6 @@ |
28 | 28 | protected $safeDelay; // integer number of seconds |
29 | 29 | |
30 | 30 | protected $session = 0; // random integer |
31 | | - /** @var Array Map of (locked key => lock type => count) */ |
32 | | - protected $locksHeld = array(); |
33 | 31 | /** @var Array Map Database connections (DB name => Database) */ |
34 | 32 | protected $conns = array(); |
35 | 33 | |
— | — | @@ -86,66 +84,72 @@ |
87 | 85 | $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 ); |
88 | 86 | } |
89 | 87 | |
90 | | - protected function doLock( array $keys, $type ) { |
| 88 | + /** |
| 89 | + * @see LockManager::doLock() |
| 90 | + */ |
| 91 | + protected function doLock( array $paths, $type ) { |
91 | 92 | $status = Status::newGood(); |
92 | 93 | |
93 | | - $keysToLock = array(); |
| 94 | + $pathsToLock = array(); |
94 | 95 | // Get locks that need to be acquired (buckets => locks)... |
95 | | - foreach ( $keys as $key ) { |
96 | | - if ( isset( $this->locksHeld[$key][$type] ) ) { |
97 | | - ++$this->locksHeld[$key][$type]; |
98 | | - } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) { |
99 | | - $this->locksHeld[$key][$type] = 1; |
| 96 | + foreach ( $paths as $path ) { |
| 97 | + if ( isset( $this->locksHeld[$path][$type] ) ) { |
| 98 | + ++$this->locksHeld[$path][$type]; |
| 99 | + } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) { |
| 100 | + $this->locksHeld[$path][$type] = 1; |
100 | 101 | } else { |
101 | | - $bucket = $this->getBucketFromKey( $key ); |
102 | | - $keysToLock[$bucket][] = $key; |
| 102 | + $bucket = $this->getBucketFromKey( $path ); |
| 103 | + $pathsToLock[$bucket][] = $path; |
103 | 104 | } |
104 | 105 | } |
105 | 106 | |
106 | | - $lockedKeys = array(); // files locked in this attempt |
| 107 | + $lockedPaths = array(); // files locked in this attempt |
107 | 108 | // Attempt to acquire these locks... |
108 | | - foreach ( $keysToLock as $bucket => $keys ) { |
| 109 | + foreach ( $pathsToLock as $bucket => $paths ) { |
109 | 110 | // Try to acquire the locks for this bucket |
110 | | - $res = $this->doLockingQueryAll( $bucket, $keys, $type ); |
| 111 | + $res = $this->doLockingQueryAll( $bucket, $paths, $type ); |
111 | 112 | if ( $res === 'cantacquire' ) { |
112 | 113 | // Resources already locked by another process. |
113 | 114 | // Abort and unlock everything we just locked. |
114 | | - $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) ); |
115 | | - $status->merge( $this->doUnlock( $lockedKeys, $type ) ); |
| 115 | + $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) ); |
| 116 | + $status->merge( $this->doUnlock( $lockedPaths, $type ) ); |
116 | 117 | return $status; |
117 | 118 | } elseif ( $res !== true ) { |
118 | 119 | // Couldn't contact any DBs for this bucket. |
119 | 120 | // Abort and unlock everything we just locked. |
120 | 121 | $status->fatal( 'lockmanager-fail-db-bucket', $bucket ); |
121 | | - $status->merge( $this->doUnlock( $lockedKeys, $type ) ); |
| 122 | + $status->merge( $this->doUnlock( $lockedPaths, $type ) ); |
122 | 123 | return $status; |
123 | 124 | } |
124 | 125 | // Record these locks as active |
125 | | - foreach ( $keys as $key ) { |
126 | | - $this->locksHeld[$key][$type] = 1; // locked |
| 126 | + foreach ( $paths as $path ) { |
| 127 | + $this->locksHeld[$path][$type] = 1; // locked |
127 | 128 | } |
128 | 129 | // Keep track of what locks were made in this attempt |
129 | | - $lockedKeys = array_merge( $lockedKeys, $keys ); |
| 130 | + $lockedPaths = array_merge( $lockedPaths, $paths ); |
130 | 131 | } |
131 | 132 | |
132 | 133 | return $status; |
133 | 134 | } |
134 | 135 | |
135 | | - protected function doUnlock( array $keys, $type ) { |
| 136 | + /** |
| 137 | + * @see LockManager::doUnlock() |
| 138 | + */ |
| 139 | + protected function doUnlock( array $paths, $type ) { |
136 | 140 | $status = Status::newGood(); |
137 | 141 | |
138 | | - foreach ( $keys as $key ) { |
139 | | - if ( !isset( $this->locksHeld[$key] ) ) { |
140 | | - $status->warning( 'lockmanager-notlocked', $key ); |
141 | | - } elseif ( !isset( $this->locksHeld[$key][$type] ) ) { |
142 | | - $status->warning( 'lockmanager-notlocked', $key ); |
| 142 | + foreach ( $paths as $path ) { |
| 143 | + if ( !isset( $this->locksHeld[$path] ) ) { |
| 144 | + $status->warning( 'lockmanager-notlocked', $path ); |
| 145 | + } elseif ( !isset( $this->locksHeld[$path][$type] ) ) { |
| 146 | + $status->warning( 'lockmanager-notlocked', $path ); |
143 | 147 | } else { |
144 | | - --$this->locksHeld[$key][$type]; |
145 | | - if ( $this->locksHeld[$key][$type] <= 0 ) { |
146 | | - unset( $this->locksHeld[$key][$type] ); |
| 148 | + --$this->locksHeld[$path][$type]; |
| 149 | + if ( $this->locksHeld[$path][$type] <= 0 ) { |
| 150 | + unset( $this->locksHeld[$path][$type] ); |
147 | 151 | } |
148 | | - if ( !count( $this->locksHeld[$key] ) ) { |
149 | | - unset( $this->locksHeld[$key] ); // no SH or EX locks left for key |
| 152 | + if ( !count( $this->locksHeld[$path] ) ) { |
| 153 | + unset( $this->locksHeld[$path] ); // no SH or EX locks left for key |
150 | 154 | } |
151 | 155 | } |
152 | 156 | } |
— | — | @@ -159,21 +163,23 @@ |
160 | 164 | } |
161 | 165 | |
162 | 166 | /** |
163 | | - * Get a connection to a lock DB and acquire locks on $keys. |
| 167 | + * Get a connection to a lock DB and acquire locks on $paths. |
164 | 168 | * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118. |
165 | 169 | * |
166 | 170 | * @param $lockDb string |
167 | | - * @param $keys Array |
| 171 | + * @param $paths Array |
168 | 172 | * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH |
169 | 173 | * @return bool Resources able to be locked |
170 | 174 | * @throws DBError |
171 | 175 | */ |
172 | | - protected function doLockingQuery( $lockDb, array $keys, $type ) { |
| 176 | + protected function doLockingQuery( $lockDb, array $paths, $type ) { |
173 | 177 | if ( $type == self::LOCK_EX ) { // writer locks |
174 | 178 | $db = $this->getConnection( $lockDb ); |
175 | 179 | if ( !$db ) { |
176 | 180 | return false; // bad config |
177 | 181 | } |
| 182 | + $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); |
| 183 | + # Build up values for INSERT clause |
178 | 184 | $data = array(); |
179 | 185 | foreach ( $keys as $key ) { |
180 | 186 | $data[] = array( 'fle_key' => $key ); |
— | — | @@ -189,11 +195,11 @@ |
190 | 196 | * This should avoid throwing any exceptions. |
191 | 197 | * |
192 | 198 | * @param $bucket integer |
193 | | - * @param $keys Array List of resource keys to lock |
| 199 | + * @param $paths Array List of resource keys to lock |
194 | 200 | * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH |
195 | 201 | * @return bool|string One of (true, 'cantacquire', 'dberrors') |
196 | 202 | */ |
197 | | - protected function doLockingQueryAll( $bucket, array $keys, $type ) { |
| 203 | + protected function doLockingQueryAll( $bucket, array $paths, $type ) { |
198 | 204 | $yesVotes = 0; // locks made on trustable DBs |
199 | 205 | $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining DBs |
200 | 206 | $quorum = floor( $votesLeft/2 + 1 ); // simple majority |
— | — | @@ -203,7 +209,7 @@ |
204 | 210 | if ( $this->cacheCheckFailures( $lockDb ) ) { |
205 | 211 | try { |
206 | 212 | // Attempt to acquire the lock on this DB |
207 | | - if ( !$this->doLockingQuery( $lockDb, $keys, $type ) ) { |
| 213 | + if ( !$this->doLockingQuery( $lockDb, $paths, $type ) ) { |
208 | 214 | return 'cantacquire'; // vetoed; resource locked |
209 | 215 | } |
210 | 216 | ++$yesVotes; // success for this peer |
— | — | @@ -218,7 +224,7 @@ |
219 | 225 | } |
220 | 226 | } |
221 | 227 | } |
222 | | - $votesLeft--; |
| 228 | + --$votesLeft; |
223 | 229 | $votesNeeded = $quorum - $yesVotes; |
224 | 230 | if ( $votesNeeded > $votesLeft ) { |
225 | 231 | // In "trust cache" mode we don't have to meet the quorum |
— | — | @@ -322,8 +328,8 @@ |
323 | 329 | */ |
324 | 330 | protected function cacheCheckFailures( $lockDb ) { |
325 | 331 | if ( $this->statusCache && $this->safeDelay > 0 ) { |
326 | | - $key = $this->getMissKey( $lockDb ); |
327 | | - $misses = $this->statusCache->get( $key ); |
| 332 | + $path = $this->getMissKey( $lockDb ); |
| 333 | + $misses = $this->statusCache->get( $path ); |
328 | 334 | return !$misses; |
329 | 335 | } |
330 | 336 | return true; |
— | — | @@ -337,12 +343,12 @@ |
338 | 344 | */ |
339 | 345 | protected function cacheRecordFailure( $lockDb ) { |
340 | 346 | if ( $this->statusCache && $this->safeDelay > 0 ) { |
341 | | - $key = $this->getMissKey( $lockDb ); |
342 | | - $misses = $this->statusCache->get( $key ); |
| 347 | + $path = $this->getMissKey( $lockDb ); |
| 348 | + $misses = $this->statusCache->get( $path ); |
343 | 349 | if ( $misses ) { |
344 | | - return $this->statusCache->incr( $key ); |
| 350 | + return $this->statusCache->incr( $path ); |
345 | 351 | } else { |
346 | | - return $this->statusCache->add( $key, 1, $this->safeDelay ); |
| 352 | + return $this->statusCache->add( $path, 1, $this->safeDelay ); |
347 | 353 | } |
348 | 354 | } |
349 | 355 | return true; |
— | — | @@ -359,14 +365,14 @@ |
360 | 366 | } |
361 | 367 | |
362 | 368 | /** |
363 | | - * Get the bucket for lock key. |
| 369 | + * Get the bucket for resource path. |
364 | 370 | * This should avoid throwing any exceptions. |
365 | 371 | * |
366 | | - * @param $key string (31 char hex key) |
| 372 | + * @param $path string |
367 | 373 | * @return integer |
368 | 374 | */ |
369 | | - protected function getBucketFromKey( $key ) { |
370 | | - $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits) |
| 375 | + protected function getBucketFromKey( $path ) { |
| 376 | + $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits) |
371 | 377 | return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket ); |
372 | 378 | } |
373 | 379 | |
— | — | @@ -406,11 +412,13 @@ |
407 | 413 | $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" ); |
408 | 414 | } |
409 | 415 | |
410 | | - protected function doLockingQuery( $lockDb, array $keys, $type ) { |
| 416 | + protected function doLockingQuery( $lockDb, array $paths, $type ) { |
411 | 417 | $db = $this->getConnection( $lockDb ); |
412 | 418 | if ( !$db ) { |
413 | 419 | return false; |
414 | 420 | } |
| 421 | + $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); |
| 422 | + # Build up values for INSERT clause |
415 | 423 | $data = array(); |
416 | 424 | foreach ( $keys as $key ) { |
417 | 425 | $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session ); |
— | — | @@ -436,6 +444,7 @@ |
437 | 445 | __METHOD__ |
438 | 446 | ); |
439 | 447 | if ( !$blocked ) { |
| 448 | + # Build up values for INSERT clause |
440 | 449 | $data = array(); |
441 | 450 | foreach ( $keys as $key ) { |
442 | 451 | $data[] = array( 'fle_key' => $key ); |
Index: trunk/phase3/includes/filerepo/backend/lockmanager/FSLockManager.php |
— | — | @@ -21,8 +21,6 @@ |
22 | 22 | |
23 | 23 | protected $lockDir; // global dir for all servers |
24 | 24 | |
25 | | - /** @var Array Map of (locked key => lock type => count) */ |
26 | | - protected $locksHeld = array(); |
27 | 25 | /** @var Array Map of (locked key => lock type => lock file handle) */ |
28 | 26 | protected $handles = array(); |
29 | 27 | |
— | — | @@ -30,22 +28,22 @@ |
31 | 29 | $this->lockDir = $config['lockDirectory']; |
32 | 30 | } |
33 | 31 | |
34 | | - protected function doLock( array $keys, $type ) { |
| 32 | + protected function doLock( array $paths, $type ) { |
35 | 33 | $status = Status::newGood(); |
36 | 34 | |
37 | | - $lockedKeys = array(); // files locked in this attempt |
38 | | - foreach ( $keys as $key ) { |
39 | | - $subStatus = $this->doSingleLock( $key, $type ); |
| 35 | + $lockedPaths = array(); // files locked in this attempt |
| 36 | + foreach ( $paths as $path ) { |
| 37 | + $subStatus = $this->doSingleLock( $path, $type ); |
40 | 38 | $status->merge( $subStatus ); |
41 | 39 | if ( $status->isOK() ) { |
42 | | - // Don't append to $lockedKeys if $key is already locked. |
| 40 | + // Don't append to $lockedPaths if $path is already locked. |
43 | 41 | // We do NOT want to unlock the key if we have to rollback. |
44 | 42 | if ( $subStatus->isGood() ) { // no warnings/fatals? |
45 | | - $lockedKeys[] = $key; |
| 43 | + $lockedPaths[] = $path; |
46 | 44 | } |
47 | 45 | } else { |
48 | 46 | // Abort and unlock everything |
49 | | - $status->merge( $this->doUnlock( $lockedKeys, $type ) ); |
| 47 | + $status->merge( $this->doUnlock( $lockedPaths, $type ) ); |
50 | 48 | return $status; |
51 | 49 | } |
52 | 50 | } |
— | — | @@ -53,11 +51,11 @@ |
54 | 52 | return $status; |
55 | 53 | } |
56 | 54 | |
57 | | - protected function doUnlock( array $keys, $type ) { |
| 55 | + protected function doUnlock( array $paths, $type ) { |
58 | 56 | $status = Status::newGood(); |
59 | 57 | |
60 | | - foreach ( $keys as $key ) { |
61 | | - $status->merge( $this->doSingleUnlock( $key, $type ) ); |
| 58 | + foreach ( $paths as $path ) { |
| 59 | + $status->merge( $this->doSingleUnlock( $path, $type ) ); |
62 | 60 | } |
63 | 61 | |
64 | 62 | return $status; |
— | — | @@ -66,38 +64,38 @@ |
67 | 65 | /** |
68 | 66 | * Lock a single resource key |
69 | 67 | * |
70 | | - * @param $key string |
| 68 | + * @param $path string |
71 | 69 | * @param $type integer |
72 | 70 | * @return Status |
73 | 71 | */ |
74 | | - protected function doSingleLock( $key, $type ) { |
| 72 | + protected function doSingleLock( $path, $type ) { |
75 | 73 | $status = Status::newGood(); |
76 | 74 | |
77 | | - if ( isset( $this->locksHeld[$key][$type] ) ) { |
78 | | - ++$this->locksHeld[$key][$type]; |
79 | | - } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) { |
80 | | - $this->locksHeld[$key][$type] = 1; |
| 75 | + if ( isset( $this->locksHeld[$path][$type] ) ) { |
| 76 | + ++$this->locksHeld[$path][$type]; |
| 77 | + } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) { |
| 78 | + $this->locksHeld[$path][$type] = 1; |
81 | 79 | } else { |
82 | 80 | wfSuppressWarnings(); |
83 | | - $handle = fopen( $this->getLockPath( $key ), 'a+' ); |
| 81 | + $handle = fopen( $this->getLockPath( $path ), 'a+' ); |
84 | 82 | wfRestoreWarnings(); |
85 | 83 | if ( !$handle ) { // lock dir missing? |
86 | 84 | wfMkdirParents( $this->lockDir ); |
87 | | - $handle = fopen( $this->getLockPath( $key ), 'a+' ); // try again |
| 85 | + $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again |
88 | 86 | } |
89 | 87 | if ( $handle ) { |
90 | 88 | // Either a shared or exclusive lock |
91 | 89 | $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX; |
92 | 90 | if ( flock( $handle, $lock | LOCK_NB ) ) { |
93 | 91 | // Record this lock as active |
94 | | - $this->locksHeld[$key][$type] = 1; |
95 | | - $this->handles[$key][$type] = $handle; |
| 92 | + $this->locksHeld[$path][$type] = 1; |
| 93 | + $this->handles[$path][$type] = $handle; |
96 | 94 | } else { |
97 | 95 | fclose( $handle ); |
98 | | - $status->fatal( 'lockmanager-fail-acquirelock', $key ); |
| 96 | + $status->fatal( 'lockmanager-fail-acquirelock', $path ); |
99 | 97 | } |
100 | 98 | } else { |
101 | | - $status->fatal( 'lockmanager-fail-openlock', $key ); |
| 99 | + $status->fatal( 'lockmanager-fail-openlock', $path ); |
102 | 100 | } |
103 | 101 | } |
104 | 102 | |
— | — | @@ -107,28 +105,28 @@ |
108 | 106 | /** |
109 | 107 | * Unlock a single resource key |
110 | 108 | * |
111 | | - * @param $key string |
| 109 | + * @param $path string |
112 | 110 | * @param $type integer |
113 | 111 | * @return Status |
114 | 112 | */ |
115 | | - protected function doSingleUnlock( $key, $type ) { |
| 113 | + protected function doSingleUnlock( $path, $type ) { |
116 | 114 | $status = Status::newGood(); |
117 | 115 | |
118 | | - if ( !isset( $this->locksHeld[$key] ) ) { |
119 | | - $status->warning( 'lockmanager-notlocked', $key ); |
120 | | - } elseif ( !isset( $this->locksHeld[$key][$type] ) ) { |
121 | | - $status->warning( 'lockmanager-notlocked', $key ); |
| 116 | + if ( !isset( $this->locksHeld[$path] ) ) { |
| 117 | + $status->warning( 'lockmanager-notlocked', $path ); |
| 118 | + } elseif ( !isset( $this->locksHeld[$path][$type] ) ) { |
| 119 | + $status->warning( 'lockmanager-notlocked', $path ); |
122 | 120 | } else { |
123 | 121 | $handlesToClose = array(); |
124 | | - --$this->locksHeld[$key][$type]; |
125 | | - if ( $this->locksHeld[$key][$type] <= 0 ) { |
126 | | - unset( $this->locksHeld[$key][$type] ); |
| 122 | + --$this->locksHeld[$path][$type]; |
| 123 | + if ( $this->locksHeld[$path][$type] <= 0 ) { |
| 124 | + unset( $this->locksHeld[$path][$type] ); |
127 | 125 | // If a LOCK_SH comes in while we have a LOCK_EX, we don't |
128 | 126 | // actually add a handler, so check for handler existence. |
129 | | - if ( isset( $this->handles[$key][$type] ) ) { |
| 127 | + if ( isset( $this->handles[$path][$type] ) ) { |
130 | 128 | // Mark this handle to be unlocked and closed |
131 | | - $handlesToClose[] = $this->handles[$key][$type]; |
132 | | - unset( $this->handles[$key][$type] ); |
| 129 | + $handlesToClose[] = $this->handles[$path][$type]; |
| 130 | + unset( $this->handles[$path][$type] ); |
133 | 131 | } |
134 | 132 | } |
135 | 133 | // Unlock handles to release locks and delete |
— | — | @@ -136,62 +134,64 @@ |
137 | 135 | if ( wfIsWindows() ) { |
138 | 136 | // Windows: for any process, including this one, |
139 | 137 | // calling unlink() on a locked file will fail |
140 | | - $status->merge( $this->closeLockHandles( $key, $handlesToClose ) ); |
141 | | - $status->merge( $this->pruneKeyLockFiles( $key ) ); |
| 138 | + $status->merge( $this->closeLockHandles( $path, $handlesToClose ) ); |
| 139 | + $status->merge( $this->pruneKeyLockFiles( $path ) ); |
142 | 140 | } else { |
143 | 141 | // Unix: unlink() can be used on files currently open by this |
144 | 142 | // process and we must do so in order to avoid race conditions |
145 | | - $status->merge( $this->pruneKeyLockFiles( $key ) ); |
146 | | - $status->merge( $this->closeLockHandles( $key, $handlesToClose ) ); |
| 143 | + $status->merge( $this->pruneKeyLockFiles( $path ) ); |
| 144 | + $status->merge( $this->closeLockHandles( $path, $handlesToClose ) ); |
147 | 145 | } |
148 | 146 | } |
149 | 147 | |
150 | 148 | return $status; |
151 | 149 | } |
152 | 150 | |
153 | | - private function closeLockHandles( $key, array $handlesToClose ) { |
| 151 | + private function closeLockHandles( $path, array $handlesToClose ) { |
154 | 152 | $status = Status::newGood(); |
155 | 153 | foreach ( $handlesToClose as $handle ) { |
156 | 154 | wfSuppressWarnings(); |
157 | 155 | if ( !flock( $handle, LOCK_UN ) ) { |
158 | | - $status->fatal( 'lockmanager-fail-releaselock', $key ); |
| 156 | + $status->fatal( 'lockmanager-fail-releaselock', $path ); |
159 | 157 | } |
160 | 158 | if ( !fclose( $handle ) ) { |
161 | | - $status->warning( 'lockmanager-fail-closelock', $key ); |
| 159 | + $status->warning( 'lockmanager-fail-closelock', $path ); |
162 | 160 | } |
163 | 161 | wfRestoreWarnings(); |
164 | 162 | } |
165 | 163 | return $status; |
166 | 164 | } |
167 | 165 | |
168 | | - private function pruneKeyLockFiles( $key ) { |
| 166 | + private function pruneKeyLockFiles( $path ) { |
169 | 167 | $status = Status::newGood(); |
170 | | - if ( !count( $this->locksHeld[$key] ) ) { |
| 168 | + if ( !count( $this->locksHeld[$path] ) ) { |
171 | 169 | wfSuppressWarnings(); |
172 | 170 | # No locks are held for the lock file anymore |
173 | | - if ( !unlink( $this->getLockPath( $key ) ) ) { |
174 | | - $status->warning( 'lockmanager-fail-deletelock', $key ); |
| 171 | + if ( !unlink( $this->getLockPath( $path ) ) ) { |
| 172 | + $status->warning( 'lockmanager-fail-deletelock', $path ); |
175 | 173 | } |
176 | 174 | wfRestoreWarnings(); |
177 | | - unset( $this->locksHeld[$key] ); |
178 | | - unset( $this->handles[$key] ); |
| 175 | + unset( $this->locksHeld[$path] ); |
| 176 | + unset( $this->handles[$path] ); |
179 | 177 | } |
180 | 178 | return $status; |
181 | 179 | } |
182 | 180 | |
183 | 181 | /** |
184 | 182 | * Get the path to the lock file for a key |
185 | | - * @param $key string |
| 183 | + * @param $path string |
186 | 184 | * @return string |
187 | 185 | */ |
188 | | - protected function getLockPath( $key ) { |
189 | | - return "{$this->lockDir}/{$key}.lock"; |
| 186 | + protected function getLockPath( $path ) { |
| 187 | + $hash = self::sha1Base36( $path ); |
| 188 | + return "{$this->lockDir}/{$hash}.lock"; |
190 | 189 | } |
191 | 190 | |
192 | 191 | function __destruct() { |
193 | 192 | // Make sure remaining locks get cleared for sanity |
194 | | - foreach ( $this->locksHeld as $key => $locks ) { |
195 | | - $this->doSingleUnlock( $key, 0 ); |
| 193 | + foreach ( $this->locksHeld as $path => $locks ) { |
| 194 | + $this->doSingleUnlock( $path, self::LOCK_EX ); |
| 195 | + $this->doSingleUnlock( $path, self::LOCK_SH ); |
196 | 196 | } |
197 | 197 | } |
198 | 198 | } |
Index: trunk/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php |
— | — | @@ -25,8 +25,6 @@ |
26 | 26 | /** @var Array Map of bucket indexes to peer server lists */ |
27 | 27 | protected $srvsByBucket; // (bucket index => (lsrv1, lsrv2, ...)) |
28 | 28 | |
29 | | - /** @var Array Map of (locked key => lock type => count) */ |
30 | | - protected $locksHeld = array(); |
31 | 29 | /** @var Array Map Server connections (server name => resource) */ |
32 | 30 | protected $conns = array(); |
33 | 31 | |
— | — | @@ -66,66 +64,66 @@ |
67 | 65 | $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 ); |
68 | 66 | } |
69 | 67 | |
70 | | - protected function doLock( array $keys, $type ) { |
| 68 | + protected function doLock( array $paths, $type ) { |
71 | 69 | $status = Status::newGood(); |
72 | 70 | |
73 | | - $keysToLock = array(); |
| 71 | + $pathsToLock = array(); |
74 | 72 | // Get locks that need to be acquired (buckets => locks)... |
75 | | - foreach ( $keys as $key ) { |
76 | | - if ( isset( $this->locksHeld[$key][$type] ) ) { |
77 | | - ++$this->locksHeld[$key][$type]; |
78 | | - } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) { |
79 | | - $this->locksHeld[$key][$type] = 1; |
| 73 | + foreach ( $paths as $path ) { |
| 74 | + if ( isset( $this->locksHeld[$path][$type] ) ) { |
| 75 | + ++$this->locksHeld[$path][$type]; |
| 76 | + } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) { |
| 77 | + $this->locksHeld[$path][$type] = 1; |
80 | 78 | } else { |
81 | | - $bucket = $this->getBucketFromKey( $key ); |
82 | | - $keysToLock[$bucket][] = $key; |
| 79 | + $bucket = $this->getBucketFromKey( $path ); |
| 80 | + $pathsToLock[$bucket][] = $path; |
83 | 81 | } |
84 | 82 | } |
85 | 83 | |
86 | | - $lockedKeys = array(); // files locked in this attempt |
| 84 | + $lockedPaths = array(); // files locked in this attempt |
87 | 85 | // Attempt to acquire these locks... |
88 | | - foreach ( $keysToLock as $bucket => $keys ) { |
| 86 | + foreach ( $pathsToLock as $bucket => $paths ) { |
89 | 87 | // Try to acquire the locks for this bucket |
90 | | - $res = $this->doLockingRequestAll( $bucket, $keys, $type ); |
| 88 | + $res = $this->doLockingRequestAll( $bucket, $paths, $type ); |
91 | 89 | if ( $res === 'cantacquire' ) { |
92 | 90 | // Resources already locked by another process. |
93 | 91 | // Abort and unlock everything we just locked. |
94 | | - $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) ); |
95 | | - $status->merge( $this->doUnlock( $lockedKeys, $type ) ); |
| 92 | + $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) ); |
| 93 | + $status->merge( $this->doUnlock( $lockedPaths, $type ) ); |
96 | 94 | return $status; |
97 | 95 | } elseif ( $res !== true ) { |
98 | 96 | // Couldn't contact any servers for this bucket. |
99 | 97 | // Abort and unlock everything we just locked. |
100 | | - $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) ); |
101 | | - $status->merge( $this->doUnlock( $lockedKeys, $type ) ); |
| 98 | + $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $paths ) ); |
| 99 | + $status->merge( $this->doUnlock( $lockedPaths, $type ) ); |
102 | 100 | return $status; |
103 | 101 | } |
104 | 102 | // Record these locks as active |
105 | | - foreach ( $keys as $key ) { |
106 | | - $this->locksHeld[$key][$type] = 1; // locked |
| 103 | + foreach ( $paths as $path ) { |
| 104 | + $this->locksHeld[$path][$type] = 1; // locked |
107 | 105 | } |
108 | 106 | // Keep track of what locks were made in this attempt |
109 | | - $lockedKeys = array_merge( $lockedKeys, $keys ); |
| 107 | + $lockedPaths = array_merge( $lockedPaths, $paths ); |
110 | 108 | } |
111 | 109 | |
112 | 110 | return $status; |
113 | 111 | } |
114 | 112 | |
115 | | - protected function doUnlock( array $keys, $type ) { |
| 113 | + protected function doUnlock( array $paths, $type ) { |
116 | 114 | $status = Status::newGood(); |
117 | 115 | |
118 | | - foreach ( $keys as $key ) { |
119 | | - if ( !isset( $this->locksHeld[$key] ) ) { |
120 | | - $status->warning( 'lockmanager-notlocked', $key ); |
121 | | - } elseif ( !isset( $this->locksHeld[$key][$type] ) ) { |
122 | | - $status->warning( 'lockmanager-notlocked', $key ); |
| 116 | + foreach ( $paths as $path ) { |
| 117 | + if ( !isset( $this->locksHeld[$path] ) ) { |
| 118 | + $status->warning( 'lockmanager-notlocked', $path ); |
| 119 | + } elseif ( !isset( $this->locksHeld[$path][$type] ) ) { |
| 120 | + $status->warning( 'lockmanager-notlocked', $path ); |
123 | 121 | } else { |
124 | | - --$this->locksHeld[$key][$type]; |
125 | | - if ( $this->locksHeld[$key][$type] <= 0 ) { |
126 | | - unset( $this->locksHeld[$key][$type] ); |
| 122 | + --$this->locksHeld[$path][$type]; |
| 123 | + if ( $this->locksHeld[$path][$type] <= 0 ) { |
| 124 | + unset( $this->locksHeld[$path][$type] ); |
127 | 125 | } |
128 | | - if ( !count( $this->locksHeld[$key] ) ) { |
129 | | - unset( $this->locksHeld[$key] ); // no SH or EX locks left for key |
| 126 | + if ( !count( $this->locksHeld[$path] ) ) { |
| 127 | + unset( $this->locksHeld[$path] ); // no SH or EX locks left for key |
130 | 128 | } |
131 | 129 | } |
132 | 130 | } |
— | — | @@ -139,14 +137,14 @@ |
140 | 138 | } |
141 | 139 | |
142 | 140 | /** |
143 | | - * Get a connection to a lock server and acquire locks on $keys |
| 141 | + * Get a connection to a lock server and acquire locks on $paths |
144 | 142 | * |
145 | 143 | * @param $lockSrv string |
146 | | - * @param $keys Array |
| 144 | + * @param $paths Array |
147 | 145 | * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH |
148 | 146 | * @return bool Resources able to be locked |
149 | 147 | */ |
150 | | - protected function doLockingRequest( $lockSrv, array $keys, $type ) { |
| 148 | + protected function doLockingRequest( $lockSrv, array $paths, $type ) { |
151 | 149 | if ( $type == self::LOCK_SH ) { // reader locks |
152 | 150 | $type = 'SH'; |
153 | 151 | } elseif ( $type == self::LOCK_EX ) { // writer locks |
— | — | @@ -156,6 +154,7 @@ |
157 | 155 | } |
158 | 156 | |
159 | 157 | // Send out the command and get the response... |
| 158 | + $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); |
160 | 159 | $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys ); |
161 | 160 | |
162 | 161 | return ( $response === 'ACQUIRED' ); |
— | — | @@ -195,25 +194,25 @@ |
196 | 195 | * Attempt to acquire locks with the peers for a bucket |
197 | 196 | * |
198 | 197 | * @param $bucket integer |
199 | | - * @param $keys Array List of resource keys to lock |
| 198 | + * @param $paths Array List of resource keys to lock |
200 | 199 | * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH |
201 | 200 | * @return bool|string One of (true, 'cantacquire', 'srverrors') |
202 | 201 | */ |
203 | | - protected function doLockingRequestAll( $bucket, array $keys, $type ) { |
| 202 | + protected function doLockingRequestAll( $bucket, array $paths, $type ) { |
204 | 203 | $yesVotes = 0; // locks made on trustable servers |
205 | 204 | $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers |
206 | 205 | $quorum = floor( $votesLeft/2 + 1 ); // simple majority |
207 | 206 | // Get votes for each peer, in order, until we have enough... |
208 | 207 | foreach ( $this->srvsByBucket[$bucket] as $index => $lockSrv ) { |
209 | 208 | // Attempt to acquire the lock on this peer |
210 | | - if ( !$this->doLockingRequest( $lockSrv, $keys, $type ) ) { |
| 209 | + if ( !$this->doLockingRequest( $lockSrv, $paths, $type ) ) { |
211 | 210 | return 'cantacquire'; // vetoed; resource locked |
212 | 211 | } |
213 | 212 | ++$yesVotes; // success for this peer |
214 | 213 | if ( $yesVotes >= $quorum ) { |
215 | 214 | return true; // lock obtained |
216 | 215 | } |
217 | | - $votesLeft--; |
| 216 | + --$votesLeft; |
218 | 217 | $votesNeeded = $quorum - $yesVotes; |
219 | 218 | if ( $votesNeeded > $votesLeft ) { |
220 | 219 | // In "trust cache" mode we don't have to meet the quorum |
— | — | @@ -265,14 +264,15 @@ |
266 | 265 | } |
267 | 266 | |
268 | 267 | /** |
269 | | - * Get the bucket for lock key |
| 268 | + * Get the bucket for resource path. |
| 269 | + * This should avoid throwing any exceptions. |
270 | 270 | * |
271 | | - * @param $key string (31 char hex key) |
| 271 | + * @param $path string |
272 | 272 | * @return integer |
273 | 273 | */ |
274 | | - protected function getBucketFromKey( $key ) { |
275 | | - $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits) |
276 | | - return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->srvsByBucket ); |
| 274 | + protected function getBucketFromKey( $path ) { |
| 275 | + $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits) |
| 276 | + return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket ); |
277 | 277 | } |
278 | 278 | |
279 | 279 | /** |
Index: trunk/phase3/includes/filerepo/backend/lockmanager/LockManager.php |
— | — | @@ -20,11 +20,6 @@ |
21 | 21 | * @since 1.19 |
22 | 22 | */ |
23 | 23 | abstract class LockManager { |
24 | | - /* Lock types; stronger locks have higher values */ |
25 | | - const LOCK_SH = 1; // shared lock (for reads) |
26 | | - const LOCK_UW = 2; // shared lock (for reads used to write elsewhere) |
27 | | - const LOCK_EX = 3; // exclusive lock (for writes) |
28 | | - |
29 | 24 | /** @var Array Mapping of lock types to the type actually used */ |
30 | 25 | protected $lockTypeMap = array( |
31 | 26 | self::LOCK_SH => self::LOCK_SH, |
— | — | @@ -32,6 +27,14 @@ |
33 | 28 | self::LOCK_EX => self::LOCK_EX |
34 | 29 | ); |
35 | 30 | |
| 31 | + /** @var Array Map of (resource path => lock type => count) */ |
| 32 | + protected $locksHeld = array(); |
| 33 | + |
| 34 | + /* Lock types; stronger locks have higher values */ |
| 35 | + const LOCK_SH = 1; // shared lock (for reads) |
| 36 | + const LOCK_UW = 2; // shared lock (for reads used to write elsewhere) |
| 37 | + const LOCK_EX = 3; // exclusive lock (for writes) |
| 38 | + |
36 | 39 | /** |
37 | 40 | * Construct a new instance from configuration |
38 | 41 | * |
— | — | @@ -47,8 +50,7 @@ |
48 | 51 | * @return Status |
49 | 52 | */ |
50 | 53 | final public function lock( array $paths, $type = self::LOCK_EX ) { |
51 | | - $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); |
52 | | - return $this->doLock( $keys, $this->lockTypeMap[$type] ); |
| 54 | + return $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] ); |
53 | 55 | } |
54 | 56 | |
55 | 57 | /** |
— | — | @@ -59,8 +61,7 @@ |
60 | 62 | * @return Status |
61 | 63 | */ |
62 | 64 | final public function unlock( array $paths, $type = self::LOCK_EX ) { |
63 | | - $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); |
64 | | - return $this->doUnlock( $keys, $this->lockTypeMap[$type] ); |
| 65 | + return $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] ); |
65 | 66 | } |
66 | 67 | |
67 | 68 | /** |
— | — | @@ -76,20 +77,20 @@ |
77 | 78 | /** |
78 | 79 | * Lock resources with the given keys and lock type |
79 | 80 | * |
80 | | - * @param $key Array List of keys to lock (40 char hex hashes) |
| 81 | + * @param $paths Array List of storage paths |
81 | 82 | * @param $type integer LockManager::LOCK_* constant |
82 | 83 | * @return string |
83 | 84 | */ |
84 | | - abstract protected function doLock( array $keys, $type ); |
| 85 | + abstract protected function doLock( array $paths, $type ); |
85 | 86 | |
86 | 87 | /** |
87 | 88 | * Unlock resources with the given keys and lock type |
88 | 89 | * |
89 | | - * @param $key Array List of keys to unlock (40 char hex hashes) |
| 90 | + * @param $paths Array List of storage paths |
90 | 91 | * @param $type integer LockManager::LOCK_* constant |
91 | 92 | * @return string |
92 | 93 | */ |
93 | | - abstract protected function doUnlock( array $keys, $type ); |
| 94 | + abstract protected function doUnlock( array $paths, $type ); |
94 | 95 | } |
95 | 96 | |
96 | 97 | /** |
— | — | @@ -162,11 +163,11 @@ |
163 | 164 | * Simple version of LockManager that does nothing |
164 | 165 | */ |
165 | 166 | class NullLockManager extends LockManager { |
166 | | - protected function doLock( array $keys, $type ) { |
| 167 | + protected function doLock( array $paths, $type ) { |
167 | 168 | return Status::newGood(); |
168 | 169 | } |
169 | 170 | |
170 | | - protected function doUnlock( array $keys, $type ) { |
| 171 | + protected function doUnlock( array $paths, $type ) { |
171 | 172 | return Status::newGood(); |
172 | 173 | } |
173 | 174 | } |