Changeset View
Changeset View
Standalone View
Standalone View
src/filesystem/PhutilFileLock.php
- This file was added.
<?php | |||||
/** | |||||
* Wrapper around `flock()` for advisory filesystem locking. Usage is | |||||
* straightforward: | |||||
* | |||||
* $path = '/path/to/lock.file'; | |||||
* $lock = PhutilFileLock::newForPath($path); | |||||
* $lock->lock(); | |||||
* | |||||
* do_contentious_things(); | |||||
* | |||||
* $lock->unlock(); | |||||
* | |||||
* For more information on locks, see @{class:PhutilLock}. | |||||
* | |||||
* @task construct Constructing Locks | |||||
* @task impl Implementation | |||||
*/ | |||||
final class PhutilFileLock extends PhutilLock { | |||||
private $lockfile; | |||||
private $handle; | |||||
/* -( Constructing Locks )------------------------------------------------- */ | |||||
/** | |||||
* Create a new lock on a lockfile. The file need not exist yet. | |||||
* | |||||
* @param string The lockfile to use. | |||||
* @return PhutilFileLock New lock object. | |||||
* | |||||
* @task construct | |||||
*/ | |||||
public static function newForPath($lockfile) { | |||||
$lockfile = Filesystem::resolvePath($lockfile); | |||||
$name = 'file:'.$lockfile; | |||||
$lock = self::getLock($name); | |||||
if (!$lock) { | |||||
$lock = new PhutilFileLock($name); | |||||
$lock->lockfile = $lockfile; | |||||
self::registerLock($lock); | |||||
} | |||||
return $lock; | |||||
} | |||||
/* -( Locking )------------------------------------------------------------ */ | |||||
/** | |||||
* Acquire the lock. If lock acquisition fails because the lock is held by | |||||
* another process, throws @{class:PhutilLockException}. Other exceptions | |||||
* indicate that lock acquisition has failed for reasons unrelated to locking. | |||||
* | |||||
* If the lock is already held, this method throws. You can test the lock | |||||
* status with @{method:isLocked}. | |||||
* | |||||
* @param float Seconds to block waiting for the lock. | |||||
* @return void | |||||
* | |||||
* @task lock | |||||
*/ | |||||
protected function doLock($wait) { | |||||
$path = $this->lockfile; | |||||
$handle = @fopen($path, 'a+'); | |||||
if (!$handle) { | |||||
throw new FilesystemException( | |||||
$path, | |||||
pht("Unable to open lock '%s' for writing!", $path)); | |||||
} | |||||
$start_time = microtime(true); | |||||
do { | |||||
$would_block = null; | |||||
$ok = flock($handle, LOCK_EX | LOCK_NB, $would_block); | |||||
if ($ok) { | |||||
break; | |||||
} else { | |||||
usleep(10000); | |||||
} | |||||
} while ($wait && $wait > (microtime(true) - $start_time)); | |||||
if (!$ok) { | |||||
fclose($handle); | |||||
throw new PhutilLockException($this->getName()); | |||||
} | |||||
$this->handle = $handle; | |||||
} | |||||
/** | |||||
* Release the lock. Throws an exception on failure, e.g. if the lock is not | |||||
* currently held. | |||||
* | |||||
* @return void | |||||
* | |||||
* @task lock | |||||
*/ | |||||
protected function doUnlock() { | |||||
$ok = flock($this->handle, LOCK_UN | LOCK_NB); | |||||
if (!$ok) { | |||||
throw new Exception(pht('Unable to unlock file!')); | |||||
} | |||||
$ok = fclose($this->handle); | |||||
if (!$ok) { | |||||
throw new Exception(pht('Unable to close file!')); | |||||
} | |||||
$this->handle = null; | |||||
} | |||||
} |