[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |