Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c85bed2

Browse filesBrowse files
committed
feature #10475 [Filesystem] Added a LockHandler (lyrixx)
This PR was merged into the 2.6-dev branch. Discussion ---------- [Filesystem] Added a LockHandler | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #9357 , #3586 | License | MIT | Doc PR | https://github.com/symfony/symfony-docs/pull/3956/files Code sample: ```php /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $lockHelper = new LockHandler('/tmp/acme/hello.lock'); if (!$lockHelper->lock()) { $output->writeln('The command is already running in another process.'); return 0; } $output->writeln(sprintf('Hello <comment>%s</comment>!', $input->getArgument('who'))); for (;;) { } $output->writeln(sprintf('bye <comment>%s</comment>!', $input->getArgument('who'))); $lockHelper->unlock(); } ``` ![process-lock](https://f.cloud.github.com/assets/408368/2443205/4f0bf3e8-ae30-11e3-9bd4-78e09e2973ad.png) Commits ------- 9ad8957 [Filesystem] Added a lock handler
2 parents aeef2bc + 9ad8957 commit c85bed2
Copy full SHA for c85bed2

File tree

3 files changed

+194
-1
lines changed
Filter options

3 files changed

+194
-1
lines changed

‎src/Symfony/Component/Filesystem/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Filesystem/CHANGELOG.md
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.6.0
5+
-----
6+
7+
* added LockHandler
8+
49
2.3.12
510
------
611

@@ -10,7 +15,7 @@ CHANGELOG
1015
-----
1116

1217
* added the dumpFile() method to atomically write files
13-
18+
1419
2.2.0
1520
-----
1621

+103Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem;
13+
14+
use Symfony\Component\Filesystem\Exception\IOException;
15+
16+
/**
17+
* LockHandler class provides a simple abstraction to lock anything by means of
18+
* a file lock.
19+
*
20+
* A locked file is created based on the lock name when calling lock(). Other
21+
* lock handlers will not be able to lock the same name until it is released
22+
* (explicitly by calling release() or implicitly when the instance holding the
23+
* lock is destroyed).
24+
*
25+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
26+
* @author Romain Neutron <imprec@gmail.com>
27+
* @author Nicolas Grekas <p@tchwork.com>
28+
*/
29+
class LockHandler
30+
{
31+
private $file;
32+
private $handle;
33+
34+
/**
35+
* @param string $name The lock name
36+
* @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
37+
* @throws IOException If the lock directory could not be created or is not writable
38+
*/
39+
public function __construct($name, $lockPath = null)
40+
{
41+
$lockPath = $lockPath ?: sys_get_temp_dir();
42+
43+
if (!is_dir($lockPath)) {
44+
$fs = new Filesystem();
45+
$fs->mkdir($lockPath);
46+
}
47+
48+
if (!is_writable($lockPath)) {
49+
throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath);
50+
}
51+
52+
$name = sprintf('%s-%s', preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name));
53+
54+
$this->file = sprintf('%s/%s', $lockPath, $name);
55+
}
56+
57+
/**
58+
* Lock the resource
59+
*
60+
* @param bool $blocking wait until the lock is released
61+
* @return bool Returns true if the lock was acquired, false otherwise
62+
* @throws IOException If the lock file could not be created or opened
63+
*/
64+
public function lock($blocking = false)
65+
{
66+
if ($this->handle) {
67+
return true;
68+
}
69+
70+
// Set an error handler to not trigger the registered error handler if
71+
// the file can not be opened.
72+
set_error_handler('var_dump', 0);
73+
$this->handle = @fopen($this->file, 'c');
74+
restore_error_handler();
75+
76+
if (!$this->handle) {
77+
throw new IOException(sprintf('Unable to fopen "%s".', $this->file), 0, null, $this->file);
78+
}
79+
80+
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
81+
// https://bugs.php.net/54129
82+
if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
83+
fclose($this->handle);
84+
$this->handle = null;
85+
86+
return false;
87+
}
88+
89+
return true;
90+
}
91+
92+
/**
93+
* Release the resource
94+
*/
95+
public function release()
96+
{
97+
if ($this->handle) {
98+
flock($this->handle, LOCK_UN | LOCK_NB);
99+
fclose($this->handle);
100+
$this->handle = null;
101+
}
102+
}
103+
}
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace Symfony\Component\Filesystem\Tests;
4+
5+
use Symfony\Component\Filesystem\LockHandler;
6+
7+
class LockHandlerTest extends \PHPUnit_Framework_TestCase
8+
{
9+
/**
10+
* @expectedException Symfony\Component\Filesystem\Exception\IOException
11+
* @expectedExceptionMessage Failed to create "/a/b/c/d/e": mkdir(): Permission denied.
12+
*/
13+
public function testConstructWhenRepositoryDoesNotExist()
14+
{
15+
new LockHandler('lock', '/a/b/c/d/e');
16+
}
17+
18+
/**
19+
* @expectedException Symfony\Component\Filesystem\Exception\IOException
20+
* @expectedExceptionMessage The directory "/" is not writable.
21+
*/
22+
public function testConstructWhenRepositoryIsNotWriteable()
23+
{
24+
new LockHandler('lock', '/');
25+
}
26+
27+
public function testConstructSanitizeName()
28+
{
29+
$lock = new LockHandler('<?php echo "% hello word ! %" ?>');
30+
31+
$file = sprintf('%s/-php-echo-hello-word--4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f', sys_get_temp_dir());
32+
// ensure the file does not exist before the lock
33+
@unlink($file);
34+
35+
$lock->lock();
36+
37+
$this->assertFileExists($file);
38+
39+
$lock->release();
40+
}
41+
42+
public function testLockRelease()
43+
{
44+
$name = 'symfony-test-filesystem.lock';
45+
46+
$l1 = new LockHandler($name);
47+
$l2 = new LockHandler($name);
48+
49+
$this->assertTrue($l1->lock());
50+
$this->assertFalse($l2->lock());
51+
52+
$l1->release();
53+
54+
$this->assertTrue($l2->lock());
55+
$l2->release();
56+
}
57+
58+
public function testLockTwice()
59+
{
60+
$name = 'symfony-test-filesystem.lock';
61+
62+
$lockHandler = new LockHandler($name);
63+
64+
$this->assertTrue($lockHandler->lock());
65+
$this->assertTrue($lockHandler->lock());
66+
67+
$lockHandler->release();
68+
}
69+
70+
public function testLockIsReleased()
71+
{
72+
$name = 'symfony-test-filesystem.lock';
73+
74+
$l1 = new LockHandler($name);
75+
$l2 = new LockHandler($name);
76+
77+
$this->assertTrue($l1->lock());
78+
$this->assertFalse($l2->lock());
79+
80+
$l1 = null;
81+
82+
$this->assertTrue($l2->lock());
83+
$l2->release();
84+
}
85+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.