r105409 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r105408‎ | r105409 | r105410 >
Date:02:07, 7 December 2011
Author:aaron
Status:deferred
Tags:
Comment:
* FileBackend/FileBackendBase:
** Added FileBackendBase::getLocalReference() and switched some callers to use it
** Made a default FileBackend streamFile() implementation
* FSFileBackend:
** Added getLocalReference()
** Change is_file => file_exists in store()/copy() so that we fail on unlink() it a directory is at the destination rather than copy the file into the directory
** Made use of stream_copy_to_stream() in concatenate() and added fclose() statements to be pedantic
* FSLockManager:
** Open the lock files in mode a+
** Fixed Unix race condition in doSingleUnlock()
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/FileBackendMultiWrite.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php (modified) (history)

Diff [purge]

Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php
@@ -234,6 +234,16 @@
235235 return Status::newFatal( 'backend-fail-stream', $params['src'] );
236236 }
237237
 238+ function getLocalReference( array $params ) {
 239+ foreach ( $this->backends as $backend ) {
 240+ $fsFile = $backend->getLocalReference( $params );
 241+ if ( $fsFile ) {
 242+ return $fsFile;
 243+ }
 244+ }
 245+ return null;
 246+ }
 247+
238248 function getLocalCopy( array $params ) {
239249 foreach ( $this->backends as $backend ) {
240250 $tmpFile = $backend->getLocalCopy( $params );
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
@@ -40,7 +40,7 @@
4141 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
4242 return $status;
4343 }
44 - if ( is_file( $dest ) ) {
 44+ if ( file_exists( $dest ) ) {
4545 if ( !empty( $params['overwriteDest'] ) ) {
4646 wfSuppressWarnings();
4747 $ok = unlink( $dest );
@@ -88,7 +88,7 @@
8989 return $status;
9090 }
9191
92 - if ( is_file( $dest ) ) {
 92+ if ( file_exists( $dest ) ) {
9393 if ( !empty( $params['overwriteDest'] ) ) {
9494 wfSuppressWarnings();
9595 $ok = unlink( $dest );
@@ -237,20 +237,25 @@
238238 foreach ( $params['srcs'] as $virtualSource ) {
239239 list( $c, $source ) = $this->resolveStoragePath( $virtualSource );
240240 if ( $source === null ) {
 241+ fclose( $tmpHandle );
241242 $status->fatal( 'backend-fail-invalidpath', $virtualSource );
242243 return $status;
243244 }
244245 // Load chunk into memory (it should be a small file)
245 - $chunk = file_get_contents( $source );
246 - if ( $chunk === false ) {
 246+ $sourceHandle = fopen( $source, 'r' );
 247+ if ( $sourceHandle === false ) {
 248+ fclose( $tmpHandle );
247249 $status->fatal( 'backend-fail-read', $virtualSource );
248250 return $status;
249251 }
250252 // Append chunk to file (pass chunk size to avoid magic quotes)
251 - if ( !fwrite( $tmpHandle, $chunk, count( $chunk ) ) ) {
 253+ if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
 254+ fclose( $sourceHandle );
 255+ fclose( $tmpHandle );
252256 $status->fatal( 'backend-fail-writetemp', $tmpPath );
253257 return $status;
254258 }
 259+ fclose( $sourceHandle );
255260 }
256261 wfSuppressWarnings();
257262 if ( !fclose( $tmpHandle ) ) {
@@ -451,25 +456,12 @@
452457 return new FSFileIterator( $dir );
453458 }
454459
455 - function streamFile( array $params ) {
456 - $status = Status::newGood();
457 -
 460+ function getLocalReference( array $params ) {
458461 list( $c, $source ) = $this->resolveStoragePath( $params['src'] );
459462 if ( $source === null ) {
460 - $status->fatal( 'backend-fail-invalidpath', $params['src'] );
461 - return $status;
 463+ return null;
462464 }
463 -
464 - $extraHeaders = isset( $params['headers'] )
465 - ? $params['headers']
466 - : array();
467 - $ok = StreamFile::stream( $source, $extraHeaders, false );
468 - if ( !$ok ) {
469 - $status->fatal( 'backend-fail-stream', $params['src'] );
470 - return $status;
471 - }
472 -
473 - return $status;
 465+ return new FSFile( $source );
474466 }
475467
476468 function getLocalCopy( array $params ) {
Index: branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php
@@ -134,12 +134,12 @@
135135 $this->locksHeld[$key][$type] = 1;
136136 } else {
137137 wfSuppressWarnings();
138 - $handle = fopen( $this->getLockPath( $key ), 'c' );
 138+ $handle = fopen( $this->getLockPath( $key ), 'a+' );
139139 wfRestoreWarnings();
140140 if ( !$handle ) { // lock dir missing?
141141 wfMkdirParents( $this->lockDir );
142142 wfSuppressWarnings();
143 - $handle = fopen( $this->getLockPath( $key ), 'c' ); // try again
 143+ $handle = fopen( $this->getLockPath( $key ), 'a+' ); // try again
144144 wfRestoreWarnings();
145145 }
146146 if ( $handle ) {
@@ -176,6 +176,7 @@
177177 } elseif ( $type && !isset( $this->locksHeld[$key][$type] ) ) {
178178 $status->warning( 'lockmanager-notlocked', $key );
179179 } else {
 180+ $handlesToClose = array();
180181 foreach ( $this->locksHeld[$key] as $lockType => $count ) {
181182 if ( $type && $lockType != $type ) {
182183 continue; // only unlock locks of type $type
@@ -186,33 +187,60 @@
187188 // If a LOCK_SH comes in while we have a LOCK_EX, we don't
188189 // actually add a handler, so check for handler existence.
189190 if ( isset( $this->handles[$key][$lockType] ) ) {
190 - wfSuppressWarnings();
191 - if ( !flock( $this->handles[$key][$lockType], LOCK_UN ) ) {
192 - $status->fatal( 'lockmanager-fail-releaselock', $key );
193 - }
194 - if ( !fclose( $this->handles[$key][$lockType] ) ) {
195 - $status->warning( 'lockmanager-fail-closelock', $key );
196 - }
197 - wfRestoreWarnings();
 191+ // Mark this handle to be unlocked and closed
 192+ $handlesToClose[] = $this->handles[$key][$lockType];
198193 unset( $this->handles[$key][$lockType] );
199194 }
200195 }
201196 }
202 - if ( !count( $this->locksHeld[$key] ) ) {
203 - wfSuppressWarnings();
204 - # No locks are held for the lock file anymore
205 - if ( !unlink( $this->getLockPath( $key ) ) ) {
206 - $status->warning( 'lockmanager-fail-deletelock', $key );
207 - }
208 - wfRestoreWarnings();
209 - unset( $this->locksHeld[$key] );
210 - unset( $this->handles[$key] );
 197+ // Unlock handles to release locks and delete
 198+ // any lock files that end up with no locks on them...
 199+ if ( wfIsWindows() ) {
 200+ // Windows: for any process, including this one,
 201+ // calling unlink() on a locked file will fail
 202+ $status->merge( $this->closeLockHandles( $key, $handlesToClose ) );
 203+ $status->merge( $this->pruneKeyLockFiles( $key ) );
 204+ } else {
 205+ // Unix: unlink() can be used on files currently open by this
 206+ // process and we must do so in order to avoid race conditions
 207+ $status->merge( $this->pruneKeyLockFiles( $key ) );
 208+ $status->merge( $this->closeLockHandles( $key, $handlesToClose ) );
211209 }
212210 }
213211
214212 return $status;
215213 }
216214
 215+ private function closeLockHandles( $key, array $handlesToClose ) {
 216+ $status = Status::newGood();
 217+ foreach ( $handlesToClose as $handle ) {
 218+ wfSuppressWarnings();
 219+ if ( !flock( $handle, LOCK_UN ) ) {
 220+ $status->fatal( 'lockmanager-fail-releaselock', $key );
 221+ }
 222+ if ( !fclose( $handle ) ) {
 223+ $status->warning( 'lockmanager-fail-closelock', $key );
 224+ }
 225+ wfRestoreWarnings();
 226+ }
 227+ return $status;
 228+ }
 229+
 230+ private function pruneKeyLockFiles( $key ) {
 231+ $status = Status::newGood();
 232+ if ( !count( $this->locksHeld[$key] ) ) {
 233+ wfSuppressWarnings();
 234+ # No locks are held for the lock file anymore
 235+ if ( !unlink( $this->getLockPath( $key ) ) ) {
 236+ $status->warning( 'lockmanager-fail-deletelock', $key );
 237+ }
 238+ wfRestoreWarnings();
 239+ unset( $this->locksHeld[$key] );
 240+ unset( $this->handles[$key] );
 241+ }
 242+ return $status;
 243+ }
 244+
217245 /**
218246 * Get the path to the lock file for a key
219247 * @param $key string
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php
@@ -206,13 +206,34 @@
207207 abstract public function getFileList( array $params );
208208
209209 /**
 210+ * Returns a file system file, identical to the file at a storage path.
 211+ * The file return is either:
 212+ * a) A local copy of the file at a storage path in the backend.
 213+ * The temporary copy will have the same extension as the source.
 214+ * b) An original of the file at a storage path in the backend.
 215+ *
 216+ * Write operations should *never* be done on this file as some backends
 217+ * may do internal tracking or may be instances of FileBackendMultiWrite.
 218+ * In that later case, there are copies of the file that must stay in sync.
 219+ *
 220+ * $params include:
 221+ * src : source storage path
 222+ *
 223+ * @param Array $params
 224+ * @return FSFile|null Returns null on failure
 225+ */
 226+ public function getLocalReference( array $params ) {
 227+ return $this->getLocalCopy( $params );
 228+ }
 229+
 230+ /**
210231 * Get a local copy on disk of the file at a storage path in the backend.
211 - * The temporary copy should have the same file extension as the source.
 232+ * The temporary copy will have the same file extension as the source.
212233 * $params include:
213234 * src : source storage path
214235 *
215236 * @param Array $params
216 - * @return TempFSFile|null Temporary file or null on failure
 237+ * @return TempFSFile|null Returns null on failure
217238 */
218239 abstract public function getLocalCopy( array $params );
219240
@@ -355,14 +376,36 @@
356377 }
357378
358379 public function getFileProps( array $params ) {
359 - $tmpFile = $this->getLocalCopy( $params );
360 - if ( !$tmpFile ) {
 380+ $fsFile = $this->getLocalReference( array( 'src' => $params['src'] ) );
 381+ if ( !$fsFile ) {
361382 return FSFile::placeholderProps();
362383 } else {
363 - return $tmpFile->getProps();
 384+ return $fsFile->getProps();
364385 }
365386 }
366387
 388+ function streamFile( array $params ) {
 389+ $status = Status::newGood();
 390+
 391+ $fsFile = $this->getLocalReference( array( 'src' => $params['src'] ) );
 392+ if ( !$fsFile ) {
 393+ $status->fatal( 'backend-fail-stream', $params['src'] );
 394+ return $status;
 395+ }
 396+
 397+ $extraHeaders = isset( $params['headers'] )
 398+ ? $params['headers']
 399+ : array();
 400+
 401+ $ok = StreamFile::stream( $fsFile->getPath(), $extraHeaders, false );
 402+ if ( !$ok ) {
 403+ $status->fatal( 'backend-fail-stream', $params['src'] );
 404+ return $status;
 405+ }
 406+
 407+ return $status;
 408+ }
 409+
367410 /**
368411 * Get the list of supported operations and their corresponding FileOp classes.
369412 *

Follow-up revisions

RevisionCommit summaryAuthorDate
r105465* Improved FileBackendMultiWrite::streamFile() in handling failures....aaron22:07, 7 December 2011

Status & tagging log