r103877 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r103876‎ | r103877 | r103878 >
Date:00:32, 22 November 2011
Author:aaron
Status:deferred
Tags:
Comment:
FileLockManager:
* Simplified scheme for resource names used by locks
* Added LOCK_SH and LOCK_EX options
* Fixed FSFileLockManager::doLock() rollback logic
* Use Status objects correctly
Modified paths:
  • /branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php (modified) (history)

Diff [purge]

Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
@@ -34,7 +34,7 @@
3535
3636 list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
3737 if ( $dest === null ) {
38 - $status->fatal( "Invalid storage path {$params['dest']}." );
 38+ $status->fatal( 'backend-fail-invalidpath', $params['dest'] );
3939 return $status;
4040 }
4141
@@ -42,16 +42,16 @@
4343 if ( isset( $params['overwriteDest'] ) ) {
4444 $ok = unlink( $dest );
4545 if ( !$ok ) {
46 - $status->fatal( "Could not delete destination file." );
 46+ $status->fatal( 'backend-fail-delete', $param['dest'] );
4747 return $status;
4848 }
4949 } elseif ( isset( $params['overwriteSame'] ) ) {
5050 if ( !$this->filesAreSame( $params['source'], $dest ) ) {
51 - $status->fatal( "Non-identical destination file already exists." );
 51+ $status->fatal( 'backend-fail-notsame', $params['source'], $params['dest'] );
5252 }
5353 return $status; // do nothing; either OK or bad status
5454 } else {
55 - $status->fatal( "Destination file already exists." );
 55+ $status->fatal( 'backend-fail-alreadyexists', $params['dest'] );
5656 return $status;
5757 }
5858 } else {
@@ -62,7 +62,7 @@
6363 $ok = copy( $params['source'], $dest );
6464 wfRestoreWarnings();
6565 if ( !$ok ) {
66 - $status->fatal( "Could not copy file to destination." );
 66+ $status->fatal( 'backend-fail-copy', $params['source'], $params['dest'] );
6767 return $status;
6868 }
6969
@@ -80,12 +80,12 @@
8181
8282 list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
8383 if ( $source === null ) {
84 - $status->fatal( "Invalid storage path {$params['source']}." );
 84+ $status->fatal( 'backend-fail-invalidpath', $params['source'] );
8585 return $status;
8686 }
8787 list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
8888 if ( $dest === null ) {
89 - $status->fatal( "Invalid storage path {$params['dest']}." );
 89+ $status->fatal( 'backend-fail-invalidpath', $params['dest'] );
9090 return $status;
9191 }
9292
@@ -93,16 +93,16 @@
9494 if ( isset( $params['overwriteDest'] ) ) {
9595 $ok = unlink( $dest );
9696 if ( !$ok ) {
97 - $status->fatal( "Could not delete destination file." );
 97+ $status->fatal( 'backend-fail-delete', $params['dest'] );
9898 return $status;
9999 }
100100 } elseif ( isset( $params['overwriteSame'] ) ) {
101101 if ( !$this->filesAreSame( $source, $dest ) ) {
102 - $status->fatal( "Non-identical destination file already exists." );
 102+ $status->fatal( 'backend-fail-notsame', $params['source'], $params['dest'] );
103103 }
104104 return $status; // do nothing; either OK or bad status
105105 } else {
106 - $status->fatal( "Destination file already exists." );
 106+ $status->fatal( 'backend-fail-alreadyexists', $params['dest'] );
107107 return $status;
108108 }
109109 } else {
@@ -113,7 +113,7 @@
114114 $ok = rename( $source, $dest );
115115 wfRestoreWarnings();
116116 if ( !$ok ) {
117 - $status->fatal( "Could not move file to destination." );
 117+ $status->fatal( 'backend-fail-move', $params['source'], $params['dest'] );
118118 return $status;
119119 }
120120
@@ -125,13 +125,13 @@
126126
127127 list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
128128 if ( $source === null ) {
129 - $status->fatal( "Invalid storage path {$params['source']}." );
 129+ $status->fatal( 'backend-fail-invalidpath', $params['source'] );
130130 return $status;
131131 }
132132
133133 if ( !file_exists( $source ) ) {
134134 if ( !$params['ignoreMissingSource'] ) {
135 - $status->fatal( "Could not delete source because it does not exist." );
 135+ $status->fatal( 'backend-fail-delete', $params['source'] );
136136 }
137137 return $status; // do nothing; either OK or bad status
138138 }
@@ -140,7 +140,7 @@
141141 $ok = unlink( $source );
142142 wfRestoreWarnings();
143143 if ( !$ok ) {
144 - $status->fatal( "Could not delete source file." );
 144+ $status->fatal( 'backend-fail-delete', $params['source'] );
145145 return $status;
146146 }
147147
@@ -152,14 +152,14 @@
153153
154154 list( $c, $dest ) = $this->resolveVirtualPath( $params['dest'] );
155155 if ( $dest === null ) {
156 - $status->fatal( "Invalid storage path {$params['dest']}." );
 156+ $status->fatal( 'backend-fail-invalidpath', $params['dest'] );
157157 return $status;
158158 }
159159
160160 // Check if the destination file exists and we can't handle that
161161 $destExists = file_exists( $dest );
162162 if ( $destExists && !$params['overwriteDest'] && !$params['overwriteSame'] ) {
163 - $status->fatal( "Destination file already exists." ); // short-circuit
 163+ $status->fatal( 'backend-fail-alreadyexists', $params['dest'] );
164164 return $status;
165165 }
166166
@@ -168,7 +168,7 @@
169169 $tmpPath = tempnam( wfTempDir(), 'file_concatenate' );
170170 wfRestoreWarnings();
171171 if ( $tmpPath === false ) {
172 - $status->fatal( "Could not create temporary file $tmpPath." );
 172+ $status->fatal( 'backend-fail-createtemp' );
173173 return $status;
174174 }
175175
@@ -177,30 +177,30 @@
178178 $tmpHandle = fopen( $tmpPath, 'a' );
179179 wfRestoreWarnings();
180180 if ( $tmpHandle === false ) {
181 - $status->fatal( "Could not open temporary file $tmpPath." );
 181+ $status->fatal( 'backend-fail-opentemp', $tmpPath );
182182 return $status;
183183 }
184184 foreach ( $params['sources'] as $virtualSource ) {
185185 list( $c, $source ) = $this->resolveVirtualPath( $virtualSource );
186186 if ( $source === null ) {
187 - $status->fatal( "Invalid storage path {$virtualSource}." );
 187+ $status->fatal( 'backend-fail-invalidpath', $virtualSource );
188188 return $status;
189189 }
190190 // Load chunk into memory (it should be a small file)
191191 $chunk = file_get_contents( $source );
192192 if ( $chunk === false ) {
193 - $status->fatal( "Could not read source file $source." );
 193+ $status->fatal( 'backend-fail-read', $virtualSource );
194194 return $status;
195195 }
196196 // Append chunk to file (pass chunk size to avoid magic quotes)
197197 if ( !fwrite( $tmpHandle, $chunk, count( $chunk ) ) ) {
198 - $status->fatal( "Could not write to temporary file $tmpPath." );
 198+ $status->fatal( 'backend-fail-writetemp', $tmpPath );
199199 return $status;
200200 }
201201 }
202202 wfSuppressWarnings();
203203 if ( !fclose( $tmpHandle ) ) {
204 - $status->fatal( "Could not close temporary file $tmpPath." );
 204+ $status->fatal( 'backend-fail-closetemp', $tmpPath );
205205 return $status;
206206 }
207207 wfRestoreWarnings();
@@ -213,12 +213,12 @@
214214 $ok = unlink( $dest );
215215 wfRestoreWarnings();
216216 if ( !$ok ) {
217 - $status->fatal( "Could not delete destination file." );
 217+ $status->fatal( 'backend-fail-delete', $params['dest'] );
218218 return $status;
219219 }
220220 } elseif ( isset( $params['overwriteSame'] ) ) {
221221 if ( !$this->filesAreSame( $tmpPath, $dest ) ) {
222 - $status->fatal( "Non-identical destination file already exists." );
 222+ $status->fatal( 'backend-fail-notsame', $tmpPath, $params['dest'] );
223223 }
224224 return $status; // do nothing; either OK or bad status
225225 }
@@ -232,7 +232,7 @@
233233 $ok = rename( $tmpPath, $dest );
234234 wfRestoreWarnings();
235235 if ( !$ok ) {
236 - $status->fatal( "Could not rename temporary file to destination." );
 236+ $status->fatal( 'backend-fail-move', $tmpPath, $params['dest'] );
237237 return $status;
238238 }
239239
@@ -271,13 +271,13 @@
272272
273273 list( $c, $source ) = $this->resolveVirtualPath( $params['source'] );
274274 if ( $source === null ) {
275 - $status->fatal( "Invalid storage path {$params['source']}." );
 275+ $status->fatal( 'backend-fail-invalidpath', $params['source'] );
276276 return $status;
277277 }
278278
279279 $ok = StreamFile::stream( $source, array(), false );
280280 if ( !$ok ) {
281 - $status->fatal( "Unable to stream file {$source}." );
 281+ $status->fatal( 'backend-fail-stream', $params['source'] );
282282 return $status;
283283 }
284284
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
@@ -1,74 +1,66 @@
22 <?php
33 /**
44 * FileBackend helper class for handling file locking.
5 - * Implemenations can rack of what locked in the process cache.
 5+ * Locks on resource keys can either be shared or exclusive.
 6+ *
 7+ * Implemenations can keep track of what is locked in the process cache.
68 * This can reduce hits to external resources for lock()/unlock() calls.
79 *
810 * Subclasses should avoid throwing exceptions at all costs.
911 */
1012 abstract class FileLockManager {
 13+ /* Lock types; stronger locks have high values */
 14+ const LOCK_SH = 1; // shared lock (for reads)
 15+ const LOCK_EX = 2; // exclusive lock (for writes)
 16+
1117 /**
1218 * Construct a new instance from configuration
 19+ *
1320 * @param $config Array
1421 */
15 - abstract public function __construct( array $config );
 22+ public function __construct( array $config ) {}
1623
1724 /**
18 - * Lock the files at a storage paths for a backend
 25+ * Lock the resources at the given abstract paths
1926 *
20 - * @param $backendKey string
21 - * @param $paths Array List of storage paths
 27+ * @param $paths Array List of resource names
 28+ * @param $type integer FileLockManager::LOCK_EX, FileLockManager::LOCK_SH
2229 * @return Status
2330 */
24 - final public function lock( $backendKey, array $paths ) {
25 - $keys = array();
26 - foreach ( $paths as $path ) {
27 - $keys[] = $this->getLockName( $backendKey, $path );
28 - }
29 - return $this->doLock( $keys );
 31+ final public function lock( array $paths, $type = self::LOCK_EX ) {
 32+ $keys = array_map( 'sha1', $paths );
 33+ return $this->doLock( $keys, $type );
3034 }
3135
3236 /**
33 - * Unlock the files at a storage paths for a backend
 37+ * Unlock the resources at the given abstract paths
3438 *
35 - * @param $backendKey string
3639 * @param $paths Array List of storage paths
3740 * @return Status
3841 */
39 - final public function unlock( $backendKey, array $paths ) {
40 - $keys = array();
41 - foreach ( $paths as $path ) {
42 - $keys[] = $this->getLockName( $backendKey, $path );
43 - }
44 - return $this->doUnlock( $keys );
 42+ final public function unlock( array $paths ) {
 43+ $keys = array_map( 'sha1', $paths );
 44+ return $this->doUnlock( $keys, 0 );
4545 }
4646
4747 /**
48 - * Get the lock name given backend key (type/name) and a storage path
49 - *
50 - * @param $backendKey string
51 - * @param $name string
52 - * @return string
53 - */
54 - private function getLockName( $backendKey, $name ) {
55 - return urlencode( $backendKey ) . ':' . md5( $name );
56 - }
57 -
58 - /**
5948 * Lock a resource with the given key
6049 *
61 - * @param $key Array List of keys
 50+ * @param $key Array List of keys to lock
 51+ * @param $type integer FileLockManager::LOCK_EX, FileLockManager::LOCK_SH
6252 * @return string
6353 */
64 - abstract protected function doLock( array $keys );
 54+ abstract protected function doLock( array $keys, $type );
6555
6656 /**
67 - * Unlock a resource with the given key
 57+ * Unlock a resource with the given key.
 58+ * If $type is given, then only locks of that type should be cleared.
6859 *
69 - * @param $key Array List of keys
 60+ * @param $key Array List of keys to unlock
 61+ * @param $type integer FileLockManager::LOCK_EX, FileLockManager::LOCK_SH, or 0
7062 * @return string
7163 */
72 - abstract protected function doUnlock( array $keys );
 64+ abstract protected function doUnlock( array $keys, $type );
7365 }
7466
7567 /**
@@ -79,24 +71,29 @@
8072 */
8173 class FSFileLockManager extends FileLockManager {
8274 protected $lockDir; // global dir for all servers
83 - /** @var Array Map of lock key names to lock file handlers */
 75+ /** @var Array Map of (locked key => lock type => lock file handle) */
8476 protected $handles = array();
8577
8678 function __construct( array $config ) {
8779 $this->lockDir = $config['lockDir'];
8880 }
8981
90 - function doLock( array $keys ) {
 82+ function doLock( array $keys, $type ) {
9183 $status = Status::newGood();
9284
9385 $lockedKeys = array(); // files locked in this attempt
9486 foreach ( $keys as $key ) {
95 - $status->merge( $this->doSingleLock( $key ) );
96 - if ( $status->isOk() ) {
97 - $lockedKeys[] = $key;
 87+ $subStatus = $this->doSingleLock( $key, $type );
 88+ $status->merge( $subStatus );
 89+ if ( $status->isOK() ) {
 90+ // Don't append to $lockedKeys if $key is already locked.
 91+ // We do NOT want to unlock the key if we have to rollback.
 92+ if ( $subStatus->isGood() ) { // no warnings/fatals?
 93+ $lockedKeys[] = $key;
 94+ }
9895 } else {
9996 // Abort and unlock everything
100 - $status->merge( $this->doUnlock( $lockedKeys ) );
 97+ $status->merge( $this->doUnlock( $lockedKeys, $type ) );
10198 return $status;
10299 }
103100 }
@@ -104,33 +101,44 @@
105102 return $status;
106103 }
107104
108 - function doUnlock( array $keys ) {
 105+ function doUnlock( array $keys, $type ) {
109106 $status = Status::newGood();
110107
111108 foreach ( $keys as $key ) {
112 - $status->merge( $this->doSingleUnlock( $key ) );
 109+ $status->merge( $this->doSingleUnlock( $key, $type ) );
113110 }
114111
115112 return $status;
116113 }
117114
118 - protected function doSingleLock( $key ) {
 115+ /**
 116+ * Lock a single resource key
 117+ *
 118+ * @param $key string
 119+ * @param $type integer
 120+ * @return Status
 121+ */
 122+ protected function doSingleLock( $key, $type ) {
119123 $status = Status::newGood();
120124
121 - if ( isset( $this->handles[$key] ) ) {
122 - $status->warning( 'File already locked.' );
 125+ if ( isset( $this->handles[$key][$type] ) ) {
 126+ $status->warning( 'lockmanager-alreadylocked', $key );
 127+ } elseif ( isset( $this->handles[$key][self::LOCK_EX] ) ) {
 128+ $status->warning( 'lockmanager-alreadylocked', $key );
123129 } else {
124130 wfSuppressWarnings();
125 - $handle = fopen( "{$this->lockDir}/{$key}", 'w' );
 131+ $handle = fopen( "{$this->lockDir}/{$key}", 'c' );
126132 if ( $handle ) {
127 - if ( flock( $handle, LOCK_SH ) ) {
128 - $this->handles[$key] = $handle;
 133+ // Either a shared or exclusive lock
 134+ $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
 135+ if ( flock( $handle, $lock ) ) {
 136+ $this->handles[$key][$type] = $handle;
129137 } else {
130138 fclose( $handle );
131 - $status->fatal( "Could not file acquire lock." );
 139+ $status->fatal( 'lockmanager-fail-acquirelock', $key );
132140 }
133141 } else {
134 - $status->fatal( "Could not open lock file." );
 142+ $status->fatal( 'lockmanager-fail-openlock', $key );
135143 }
136144 wfRestoreWarnings();
137145 }
@@ -138,24 +146,43 @@
139147 return $status;
140148 }
141149
142 - protected function doSingleUnlock( $key ) {
 150+ /**
 151+ * Unlock a single resource key
 152+ *
 153+ * @param $key string
 154+ * @param $type integer
 155+ * @return Status
 156+ */
 157+ protected function doSingleUnlock( $key, $type ) {
143158 $status = Status::newGood();
144159
145 - if ( isset( $this->handles[$key] ) ) {
146 - wfSuppressWarnings();
147 - if ( !flock( $this->handles[$key], LOCK_UN ) ) {
148 - $status->fatal( "Could not unlock file." );
 160+ if ( !isset( $this->handles[$key] ) ) {
 161+ $status->warning( 'lockmanager-notlocked', $key );
 162+ } elseif ( $type && !isset( $this->handles[$key][$type] ) ) {
 163+ $status->warning( 'lockmanager-notlocked', $key );
 164+ } else {
 165+ foreach ( $this->handles[$key] as $lockType => $handle ) {
 166+ if ( $type && $lockType != $type ) {
 167+ continue; // only unlock locks of type $type
 168+ }
 169+ wfSuppressWarnings();
 170+ if ( !flock( $this->handles[$key][$lockType], LOCK_UN ) ) {
 171+ $status->fatal( 'lockmanager-fail-releaselock', $key );
 172+ }
 173+ if ( !fclose( $this->handles[$key][$lockType] ) ) {
 174+ $status->warning( 'lockmanager-fail-closelock', $key );
 175+ }
 176+ wfRestoreWarnings();
 177+ unset( $this->handles[$key][$lockType] );
149178 }
150 - if ( !fclose( $this->handles[$key] ) ) {
151 - $status->warning( "Could not close lock file." );
 179+ if ( !count( $this->handles[$key] ) ) {
 180+ wfSuppressWarnings();
 181+ # No locks are held for the lock file anymore
 182+ if ( !unlink( "{$this->lockDir}/{$key}" ) ) {
 183+ $status->warning( 'lockmanager-fail-deletelock', $key );
 184+ }
 185+ wfRestoreWarnings();
152186 }
153 - if ( !unlink( "{$this->lockDir}/{$key}" ) ) {
154 - $status->warning( "Could not delete lock file." );
155 - }
156 - wfRestoreWarnings();
157 - unset( $this->handles[$key] );
158 - } else {
159 - $status->warning( "There is no file lock to unlock." );
160187 }
161188
162189 return $status;
@@ -163,9 +190,11 @@
164191
165192 protected function __destruct() {
166193 // Make sure remaining files get cleared for sanity
167 - foreach ( $this->handles as $key => $handler ) {
168 - flock( $handler, LOCK_UN ); // PHP 5.3 will not do this automatically
169 - fclose( $handler );
 194+ foreach ( $this->handles as $key => $locks ) {
 195+ foreach ( $locks as $type => $handle ) {
 196+ flock( $handle, LOCK_UN ); // PHP 5.3 will not do this automatically
 197+ fclose( $handle );
 198+ }
170199 unlink( "{$this->lockDir}/{$key}" );
171200 }
172201 }
@@ -191,8 +220,8 @@
192221 class DBFileLockManager extends FileLockManager {
193222 /** @var Array Map of bucket indexes to server names */
194223 protected $serverMap = array(); // (index => (server1,server2,...))
195 - /** @var Array List of active lock key names */
196 - protected $locksHeld = array(); // (key => 1)
 224+ /** @var Array Map of (locked key => lock type => 1) */
 225+ protected $locksHeld = array();
197226 /** $var Array Map Lock-active database connections (name => Database) */
198227 protected $activeConns = array();
199228
@@ -221,18 +250,20 @@
222251 }
223252 }
224253
225 - function doLock( array $keys ) {
 254+ function doLock( array $keys, $type ) {
226255 $status = Status::newGood();
227256
228257 $keysToLock = array();
229258 // Get locks that need to be acquired (buckets => locks)...
230259 foreach ( $keys as $key ) {
231 - if ( isset( $this->locksHeld[$key] ) ) {
232 - $status->warning( 'File already locked.' );
 260+ if ( isset( $this->locksHeld[$key][$type] ) ) {
 261+ $status->warning( 'lockmanager-alreadylocked', $key );
 262+ } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) {
 263+ $status->warning( 'lockmanager-alreadylocked', $key );
233264 } else {
234265 $bucket = $this->getBucketFromKey( $key );
235266 if ( $bucket === null ) { // config error?
236 - $status->fatal( "Lock servers for key $key is not set." );
 267+ $status->fatal( 'lockmanager-fail-config', $key );
237268 return $status;
238269 }
239270 $keysToLock[$bucket][] = $key;
@@ -249,25 +280,26 @@
250281 // (b) Server is down but a fallback is up
251282 // (c) Server is down and no fallbacks are up (or none defined)
252283 try {
253 - $this->lockingSelect( $server, $keys ); // SELECT FOR UPDATE
 284+ $this->lockingSelect( $server, $keys, $type );
254285 } catch ( DBError $e ) {
255286 // Can we manage to lock on any of the fallback servers?
256 - if ( !$this->lockingSelectFallbacks( $bucket, $keys ) ) {
 287+ if ( $this->lockingSelectFallbacks( $bucket, $keys, $type ) ) {
 288+ // Recovered; a fallback server is up
 289+ $propagateToFallbacks = false; // done already
 290+ } else {
257291 // Abort and unlock everything we just locked
258 - $status->fatal( "Could not contact the lock server." );
259 - $status->merge( $this->doUnlock( $lockedKeys ) );
 292+ $status->fatal( 'lockmanager-fail-db', $bucket );
 293+ $status->merge( $this->doUnlock( $lockedKeys, $type ) );
260294 return $status;
261 - } else { // recovered using fallbacks
262 - $propagateToFallbacks = false; // done already
263295 }
264296 }
265297 // Propagate any locks to the fallback servers (best effort)
266298 if ( $propagateToFallbacks ) {
267 - $this->lockingSelectFallbacks( $bucket, $keys );
 299+ $this->lockingSelectFallbacks( $bucket, $keys, $type );
268300 }
269301 // Record locks as active
270302 foreach ( $keys as $key ) {
271 - $this->locksHeld[$key] = 1; // locked
 303+ $this->locksHeld[$key][$type] = 1; // locked
272304 }
273305 // Keep track of what locks were made in this attempt
274306 $lockedKeys = array_merge( $lockedKeys, $keys );
@@ -276,21 +308,29 @@
277309 return $status;
278310 }
279311
280 - function doUnlock( array $keys ) {
 312+ function doUnlock( array $keys, $type ) {
281313 $status = Status::newGood();
282314
283315 foreach ( $keys as $key ) {
284 - if ( $this->locksHeld[$key] ) {
285 - unset( $this->locksHeld[$key] );
286 - // Reference count the locks held and COMMIT when zero
287 - if ( !count( $this->locksHeld ) ) {
288 - $this->commitLockTransactions();
 316+ if ( !isset( $this->locksHeld[$key] ) ) {
 317+ $status->warning( 'lockmanager-notlocked', $key );
 318+ } elseif ( $type && !isset( $this->locksHeld[$key][$type] ) ) {
 319+ $status->warning( 'lockmanager-notlocked', $key );
 320+ } else {
 321+ foreach ( $this->locksHeld[$key] as $lockType => $x ) {
 322+ if ( $type && $lockType != $type ) {
 323+ continue; // only unlock locks of type $type
 324+ }
 325+ unset( $this->locksHeld[$key][$lockType] );
289326 }
290 - } else {
291 - $status->warning( "There is no file lock to unlock." );
292327 }
293328 }
294329
 330+ // Reference count the locks held and COMMIT when zero
 331+ if ( !count( $this->locksHeld ) ) {
 332+ $this->commitLockTransactions();
 333+ }
 334+
295335 return $status;
296336 }
297337
@@ -299,19 +339,23 @@
300340 *
301341 * @param $server string
302342 * @param $keys Array
 343+ * @param $type integer FileLockManager::LOCK_EX or FileLockManager::LOCK_SH
303344 * @return void
304345 */
305 - protected function lockingSelect( $server, array $keys ) {
 346+ protected function lockingSelect( $server, array $keys, $type ) {
306347 if ( !isset( $this->activeConns[$server] ) ) {
307348 $this->activeConns[$server] = wfGetDB( DB_MASTER, array(), $server );
308349 $this->activeConns[$server]->begin(); // start transaction
309350 }
 351+ $lockingClause = ( $type == self::LOCK_SH )
 352+ ? 'LOCK IN SHARE MODE' // reader lock
 353+ : 'FOR UPDATE'; // writer lock
310354 $this->activeConns[$server]->select(
311355 'file_locking',
312356 '1',
313357 array( 'fl_key' => $keys ),
314358 __METHOD__,
315 - array( 'FOR UPDATE' )
 359+ array( $lockingClause )
316360 );
317361 }
318362
@@ -321,15 +365,16 @@
322366 *
323367 * @param $bucket integer
324368 * @param $keys Array
 369+ * @param $type integer FileLockManager::LOCK_EX or FileLockManager::LOCK_SH
325370 * @return bool Locks made on at least one fallback server
326371 */
327 - protected function lockingSelectFallbacks( $bucket, array $keys ) {
 372+ protected function lockingSelectFallbacks( $bucket, array $keys, $type ) {
328373 $locksMade = false;
329 - $count = count( $this->serverMap[$bucket] );
330 - for ( $i=1; $i < $count; $i++ ) { // start at 1 to only include fallbacks
 374+ // Start at $i=1 to only include fallback servers
 375+ for ( $i=1; $i < count( $this->serverMap[$bucket] ); $i++ ) {
331376 $server = $this->serverMap[$bucket][$i];
332377 try {
333 - $this->doLockingSelect( $server, $keys ); // SELECT FOR UPDATE
 378+ $this->doLockingSelect( $server, $keys, $type );
334379 $locksMade = true; // success for this fallback
335380 } catch ( DBError $e ) {
336381 // oh well; best effort (@TODO: logging?)
@@ -380,11 +425,11 @@
381426 class NullFileLockManager extends FileLockManager {
382427 function __construct( array $config ) {}
383428
384 - function doLock( array $keys ) {
 429+ function doLock( array $keys, $type ) {
385430 return Status::newGood();
386431 }
387432
388 - function doUnlock( array $keys ) {
 433+ function doUnlock( array $keys, $type ) {
389434 return Status::newGood();
390435 }
391436 }
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
@@ -334,18 +334,27 @@
335335 }
336336
337337 final public function lockFiles( array $paths ) {
338 - // Locks should be specific to this backend location
339 - $backendKey = get_class( $this ) . '-' . $this->getName();
340 - return $this->lockManager->lock( $backendKey, $paths ); // not supported
 338+ return $this->lockManager->lock( $this->getLockResourcePaths( $paths ) );
341339 }
342340
343341 final public function unlockFiles( array $paths ) {
344 - // Locks should be specific to this backend location
345 - $backendKey = get_class( $this ) . '-' . $this->getName();
346 - return $this->lockManager->unlock( $backendKey, $paths ); // not supported
 342+ return $this->lockManager->unlock( $this->getLockResourcePaths( $paths ) );
347343 }
348344
349345 /**
 346+ * Get a prefix to use for locking keys
 347+ * @return string
 348+ */
 349+ private function getLockResourcePaths( array $paths ) {
 350+ $backendKey = get_class( $this ) . ':' . $this->getName();
 351+ $res = array();
 352+ foreach( $paths as $path ) {
 353+ $res[] = "{$backendKey}:{$path}";
 354+ }
 355+ return $res;
 356+ }
 357+
 358+ /**
350359 * Split a storage path (e.g. "mwstore://container/path/to/object")
351360 * into a container name and a full object name within that container.
352361 *
@@ -554,7 +563,7 @@
555564 // Create a temporary backup copy...
556565 $this->tmpDestFile = $this->getLocalCopy( $this->params['dest'] );
557566 if ( !$this->tmpDestFile ) {
558 - $status->fatal( "Could not backup destination file." );
 567+ $status->fatal( 'backend-fail-restore', $this->params['dest'] );
559568 return $status;
560569 }
561570 }
@@ -750,7 +759,7 @@
751760 $status = Status::newGood();
752761 if ( !$this->params['ignoreMissingSource'] ) {
753762 if ( !$this->backend->fileExists( $this->params['source'] ) ) {
754 - $status->fatal( "Cannot delete file because it does not exist." );
 763+ $status->fatal( 'backend-fail-notexists', $this->params['source'] );
755764 return $status;
756765 }
757766 }

Status & tagging log