[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/filebackend/lockmanager/ -> FSLockManager.php (source)

   1  <?php
   2  /**
   3   * Simple version of LockManager based on using FS lock files.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup LockManager
  22   */
  23  
  24  /**
  25   * Simple version of LockManager based on using FS lock files.
  26   * All locks are non-blocking, which avoids deadlocks.
  27   *
  28   * This should work fine for small sites running off one server.
  29   * Do not use this with 'lockDirectory' set to an NFS mount unless the
  30   * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
  31   * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
  32   *
  33   * @ingroup LockManager
  34   * @since 1.19
  35   */
  36  class FSLockManager extends LockManager {
  37      /** @var array Mapping of lock types to the type actually used */
  38      protected $lockTypeMap = array(
  39          self::LOCK_SH => self::LOCK_SH,
  40          self::LOCK_UW => self::LOCK_SH,
  41          self::LOCK_EX => self::LOCK_EX
  42      );
  43  
  44      protected $lockDir; // global dir for all servers
  45  
  46      /** @var array Map of (locked key => lock file handle) */
  47      protected $handles = array();
  48  
  49      /**
  50       * Construct a new instance from configuration.
  51       *
  52       * @param array $config Includes:
  53       *   - lockDirectory : Directory containing the lock files
  54       */
  55  	function __construct( array $config ) {
  56          parent::__construct( $config );
  57  
  58          $this->lockDir = $config['lockDirectory'];
  59      }
  60  
  61      /**
  62       * @see LockManager::doLock()
  63       * @param array $paths
  64       * @param int $type
  65       * @return Status
  66       */
  67  	protected function doLock( array $paths, $type ) {
  68          $status = Status::newGood();
  69  
  70          $lockedPaths = array(); // files locked in this attempt
  71          foreach ( $paths as $path ) {
  72              $status->merge( $this->doSingleLock( $path, $type ) );
  73              if ( $status->isOK() ) {
  74                  $lockedPaths[] = $path;
  75              } else {
  76                  // Abort and unlock everything
  77                  $status->merge( $this->doUnlock( $lockedPaths, $type ) );
  78  
  79                  return $status;
  80              }
  81          }
  82  
  83          return $status;
  84      }
  85  
  86      /**
  87       * @see LockManager::doUnlock()
  88       * @param array $paths
  89       * @param int $type
  90       * @return Status
  91       */
  92  	protected function doUnlock( array $paths, $type ) {
  93          $status = Status::newGood();
  94  
  95          foreach ( $paths as $path ) {
  96              $status->merge( $this->doSingleUnlock( $path, $type ) );
  97          }
  98  
  99          return $status;
 100      }
 101  
 102      /**
 103       * Lock a single resource key
 104       *
 105       * @param string $path
 106       * @param int $type
 107       * @return Status
 108       */
 109  	protected function doSingleLock( $path, $type ) {
 110          $status = Status::newGood();
 111  
 112          if ( isset( $this->locksHeld[$path][$type] ) ) {
 113              ++$this->locksHeld[$path][$type];
 114          } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
 115              $this->locksHeld[$path][$type] = 1;
 116          } else {
 117              if ( isset( $this->handles[$path] ) ) {
 118                  $handle = $this->handles[$path];
 119              } else {
 120                  wfSuppressWarnings();
 121                  $handle = fopen( $this->getLockPath( $path ), 'a+' );
 122                  wfRestoreWarnings();
 123                  if ( !$handle ) { // lock dir missing?
 124                      wfMkdirParents( $this->lockDir );
 125                      $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
 126                  }
 127              }
 128              if ( $handle ) {
 129                  // Either a shared or exclusive lock
 130                  $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
 131                  if ( flock( $handle, $lock | LOCK_NB ) ) {
 132                      // Record this lock as active
 133                      $this->locksHeld[$path][$type] = 1;
 134                      $this->handles[$path] = $handle;
 135                  } else {
 136                      fclose( $handle );
 137                      $status->fatal( 'lockmanager-fail-acquirelock', $path );
 138                  }
 139              } else {
 140                  $status->fatal( 'lockmanager-fail-openlock', $path );
 141              }
 142          }
 143  
 144          return $status;
 145      }
 146  
 147      /**
 148       * Unlock a single resource key
 149       *
 150       * @param string $path
 151       * @param int $type
 152       * @return Status
 153       */
 154  	protected function doSingleUnlock( $path, $type ) {
 155          $status = Status::newGood();
 156  
 157          if ( !isset( $this->locksHeld[$path] ) ) {
 158              $status->warning( 'lockmanager-notlocked', $path );
 159          } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
 160              $status->warning( 'lockmanager-notlocked', $path );
 161          } else {
 162              $handlesToClose = array();
 163              --$this->locksHeld[$path][$type];
 164              if ( $this->locksHeld[$path][$type] <= 0 ) {
 165                  unset( $this->locksHeld[$path][$type] );
 166              }
 167              if ( !count( $this->locksHeld[$path] ) ) {
 168                  unset( $this->locksHeld[$path] ); // no locks on this path
 169                  if ( isset( $this->handles[$path] ) ) {
 170                      $handlesToClose[] = $this->handles[$path];
 171                      unset( $this->handles[$path] );
 172                  }
 173              }
 174              // Unlock handles to release locks and delete
 175              // any lock files that end up with no locks on them...
 176              if ( wfIsWindows() ) {
 177                  // Windows: for any process, including this one,
 178                  // calling unlink() on a locked file will fail
 179                  $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
 180                  $status->merge( $this->pruneKeyLockFiles( $path ) );
 181              } else {
 182                  // Unix: unlink() can be used on files currently open by this
 183                  // process and we must do so in order to avoid race conditions
 184                  $status->merge( $this->pruneKeyLockFiles( $path ) );
 185                  $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
 186              }
 187          }
 188  
 189          return $status;
 190      }
 191  
 192      /**
 193       * @param string $path
 194       * @param array $handlesToClose
 195       * @return Status
 196       */
 197  	private function closeLockHandles( $path, array $handlesToClose ) {
 198          $status = Status::newGood();
 199          foreach ( $handlesToClose as $handle ) {
 200              if ( !flock( $handle, LOCK_UN ) ) {
 201                  $status->fatal( 'lockmanager-fail-releaselock', $path );
 202              }
 203              if ( !fclose( $handle ) ) {
 204                  $status->warning( 'lockmanager-fail-closelock', $path );
 205              }
 206          }
 207  
 208          return $status;
 209      }
 210  
 211      /**
 212       * @param string $path
 213       * @return Status
 214       */
 215  	private function pruneKeyLockFiles( $path ) {
 216          $status = Status::newGood();
 217          if ( !isset( $this->locksHeld[$path] ) ) {
 218              # No locks are held for the lock file anymore
 219              if ( !unlink( $this->getLockPath( $path ) ) ) {
 220                  $status->warning( 'lockmanager-fail-deletelock', $path );
 221              }
 222              unset( $this->handles[$path] );
 223          }
 224  
 225          return $status;
 226      }
 227  
 228      /**
 229       * Get the path to the lock file for a key
 230       * @param string $path
 231       * @return string
 232       */
 233  	protected function getLockPath( $path ) {
 234          return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
 235      }
 236  
 237      /**
 238       * Make sure remaining locks get cleared for sanity
 239       */
 240  	function __destruct() {
 241          while ( count( $this->locksHeld ) ) {
 242              foreach ( $this->locksHeld as $path => $locks ) {
 243                  $this->doSingleUnlock( $path, self::LOCK_EX );
 244                  $this->doSingleUnlock( $path, self::LOCK_SH );
 245              }
 246          }
 247      }
 248  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1