From 9ad8957a0b4e32570e24e95d0eccc1c9a2fc7982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 18 Mar 2014 00:50:08 +0100 Subject: [PATCH] [Filesystem] Added a lock handler --- src/Symfony/Component/Filesystem/CHANGELOG.md | 7 +- .../Component/Filesystem/LockHandler.php | 103 ++++++++++++++++++ .../Filesystem/Tests/LockHandlerTest.php | 85 +++++++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Filesystem/LockHandler.php create mode 100644 src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php diff --git a/src/Symfony/Component/Filesystem/CHANGELOG.md b/src/Symfony/Component/Filesystem/CHANGELOG.md index 5b5cd6a6c612d..a4c0479f7d9a7 100644 --- a/src/Symfony/Component/Filesystem/CHANGELOG.md +++ b/src/Symfony/Component/Filesystem/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.6.0 +----- + + * added LockHandler + 2.3.12 ------ @@ -10,7 +15,7 @@ CHANGELOG ----- * added the dumpFile() method to atomically write files - + 2.2.0 ----- diff --git a/src/Symfony/Component/Filesystem/LockHandler.php b/src/Symfony/Component/Filesystem/LockHandler.php new file mode 100644 index 0000000000000..a1716db4d03a1 --- /dev/null +++ b/src/Symfony/Component/Filesystem/LockHandler.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * LockHandler class provides a simple abstraction to lock anything by means of + * a file lock. + * + * A locked file is created based on the lock name when calling lock(). Other + * lock handlers will not be able to lock the same name until it is released + * (explicitly by calling release() or implicitly when the instance holding the + * lock is destroyed). + * + * @author Grégoire Pineau + * @author Romain Neutron + * @author Nicolas Grekas + */ +class LockHandler +{ + private $file; + private $handle; + + /** + * @param string $name The lock name + * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory + * @throws IOException If the lock directory could not be created or is not writable + */ + public function __construct($name, $lockPath = null) + { + $lockPath = $lockPath ?: sys_get_temp_dir(); + + if (!is_dir($lockPath)) { + $fs = new Filesystem(); + $fs->mkdir($lockPath); + } + + if (!is_writable($lockPath)) { + throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); + } + + $name = sprintf('%s-%s', preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); + + $this->file = sprintf('%s/%s', $lockPath, $name); + } + + /** + * Lock the resource + * + * @param bool $blocking wait until the lock is released + * @return bool Returns true if the lock was acquired, false otherwise + * @throws IOException If the lock file could not be created or opened + */ + public function lock($blocking = false) + { + if ($this->handle) { + return true; + } + + // Set an error handler to not trigger the registered error handler if + // the file can not be opened. + set_error_handler('var_dump', 0); + $this->handle = @fopen($this->file, 'c'); + restore_error_handler(); + + if (!$this->handle) { + throw new IOException(sprintf('Unable to fopen "%s".', $this->file), 0, null, $this->file); + } + + // On Windows, even if PHP doc says the contrary, LOCK_NB works, see + // https://bugs.php.net/54129 + if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { + fclose($this->handle); + $this->handle = null; + + return false; + } + + return true; + } + + /** + * Release the resource + */ + public function release() + { + if ($this->handle) { + flock($this->handle, LOCK_UN | LOCK_NB); + fclose($this->handle); + $this->handle = null; + } + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php b/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php new file mode 100644 index 0000000000000..e4f30a16a9780 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php @@ -0,0 +1,85 @@ +'); + + $file = sprintf('%s/-php-echo-hello-word--4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f', sys_get_temp_dir()); + // ensure the file does not exist before the lock + @unlink($file); + + $lock->lock(); + + $this->assertFileExists($file); + + $lock->release(); + } + + public function testLockRelease() + { + $name = 'symfony-test-filesystem.lock'; + + $l1 = new LockHandler($name); + $l2 = new LockHandler($name); + + $this->assertTrue($l1->lock()); + $this->assertFalse($l2->lock()); + + $l1->release(); + + $this->assertTrue($l2->lock()); + $l2->release(); + } + + public function testLockTwice() + { + $name = 'symfony-test-filesystem.lock'; + + $lockHandler = new LockHandler($name); + + $this->assertTrue($lockHandler->lock()); + $this->assertTrue($lockHandler->lock()); + + $lockHandler->release(); + } + + public function testLockIsReleased() + { + $name = 'symfony-test-filesystem.lock'; + + $l1 = new LockHandler($name); + $l2 = new LockHandler($name); + + $this->assertTrue($l1->lock()); + $this->assertFalse($l2->lock()); + + $l1 = null; + + $this->assertTrue($l2->lock()); + $l2->release(); + } +}