Skip to content

[Filesystem] Added a LockHandler #10475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 22, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Symfony/Component/Filesystem/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

2.6.0
-----

* added LockHandler

2.3.12
------

Expand All @@ -10,7 +15,7 @@ CHANGELOG
-----

* added the dumpFile() method to atomically write files

2.2.0
-----

Expand Down
103 changes: 103 additions & 0 deletions src/Symfony/Component/Filesystem/LockHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <lyrixx@lyrixx.info>
* @author Romain Neutron <imprec@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use a subdirectory of / tmp to avoid collision with existing files?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget to use sha256 and not sha1 here.


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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you use the Filesystem class here ?


$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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing phpdocs on all methods

{
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;
}
}
}
85 changes: 85 additions & 0 deletions src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Symfony\Component\Filesystem\Tests;

use Symfony\Component\Filesystem\LockHandler;

class LockHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException Symfony\Component\Filesystem\Exception\IOException
* @expectedExceptionMessage Failed to create "/a/b/c/d/e": mkdir(): Permission denied.
*/
public function testConstructWhenRepositoryDoesNotExist()
{
new LockHandler('lock', '/a/b/c/d/e');
}

/**
* @expectedException Symfony\Component\Filesystem\Exception\IOException
* @expectedExceptionMessage The directory "/" is not writable.
*/
public function testConstructWhenRepositoryIsNotWriteable()
{
new LockHandler('lock', '/');
}

public function testConstructSanitizeName()
{
$lock = new LockHandler('<?php echo "% hello word ! %" ?>');

$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();
}
}