Skip to content

Commit ee04d72

Browse files
authored
Merge pull request #272 from php-enqueue/fs-lock-interface
[fs] Copy past Symfony's LockHandler (not awailable in Sf4).
2 parents b59e646 + 701617e commit ee04d72

File tree

6 files changed

+275
-67
lines changed

6 files changed

+275
-67
lines changed

pkg/fs/CannotObtainLockException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Enqueue\Fs;
4+
5+
use Interop\Queue\Exception;
6+
7+
class CannotObtainLockException extends Exception
8+
{
9+
}

pkg/fs/FsContext.php

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
namespace Enqueue\Fs;
44

5-
use Doctrine\ORM\Cache\Lock;
65
use Interop\Queue\InvalidDestinationException;
76
use Interop\Queue\PsrContext;
87
use Interop\Queue\PsrDestination;
98
use Interop\Queue\PsrQueue;
109
use Makasim\File\TempFile;
1110
use Symfony\Component\Filesystem\Filesystem;
12-
use Symfony\Component\Filesystem\LockHandler;
1311

1412
class FsContext implements PsrContext
1513
{
@@ -29,14 +27,14 @@ class FsContext implements PsrContext
2927
private $chmod;
3028

3129
/**
32-
* @var LockHandler[]
30+
* @var null
3331
*/
34-
private $lockHandlers;
32+
private $pollingInterval;
3533

3634
/**
37-
* @var null
35+
* @var Lock
3836
*/
39-
private $pollingInterval;
37+
private $lock;
4038

4139
/**
4240
* @param string $storeDir
@@ -54,7 +52,7 @@ public function __construct($storeDir, $preFetchCount, $chmod, $pollingInterval
5452
$this->chmod = $chmod;
5553
$this->pollingInterval = $pollingInterval;
5654

57-
$this->lockHandlers = [];
55+
$this->lock = new LegacyFilesystemLock();
5856
}
5957

6058
/**
@@ -95,11 +93,6 @@ public function declareDestination(PsrDestination $destination)
9593
InvalidDestinationException::assertDestinationInstanceOf($destination, FsDestination::class);
9694

9795
set_error_handler(function ($severity, $message, $file, $line) {
98-
// do not throw on a deprecation notice.
99-
if (E_USER_DEPRECATED === $severity && false !== strpos($message, LockHandler::class)) {
100-
return;
101-
}
102-
10396
throw new \ErrorException($message, 0, $severity, $file, $line);
10497
});
10598

@@ -123,20 +116,14 @@ public function workWithFile(FsDestination $destination, $mode, callable $callba
123116

124117
try {
125118
$file = fopen($destination->getFileInfo(), $mode);
126-
$lockHandler = $this->getLockHandler($destination);
127-
128-
if (false == $lockHandler->lock(true)) {
129-
throw new \LogicException(sprintf('Cannot obtain the lock for destination %s', $destination->getName()));
130-
}
119+
$this->lock->lock($destination);
131120

132121
return call_user_func($callback, $destination, $file);
133122
} finally {
134123
if (isset($file)) {
135124
fclose($file);
136125
}
137-
if (isset($lockHandler)) {
138-
$lockHandler->release();
139-
}
126+
$this->lock->release($destination);
140127

141128
restore_error_handler();
142129
}
@@ -184,11 +171,7 @@ public function createConsumer(PsrDestination $destination)
184171

185172
public function close()
186173
{
187-
foreach ($this->lockHandlers as $lockHandler) {
188-
$lockHandler->release();
189-
}
190-
191-
$this->lockHandlers = [];
174+
$this->lock->releaseAll();
192175
}
193176

194177
/**
@@ -234,18 +217,4 @@ private function getStoreDir()
234217

235218
return $this->storeDir;
236219
}
237-
238-
/**
239-
* @param FsDestination $destination
240-
*
241-
* @return LockHandler
242-
*/
243-
private function getLockHandler(FsDestination $destination)
244-
{
245-
if (false == isset($this->lockHandlers[$destination->getName()])) {
246-
$this->lockHandlers[$destination->getName()] = new LockHandler($destination->getName(), $this->storeDir);
247-
}
248-
249-
return $this->lockHandlers[$destination->getName()];
250-
}
251220
}

pkg/fs/LegacyFilesystemLock.php

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?php
2+
3+
namespace Enqueue\Fs;
4+
5+
use Symfony\Component\Filesystem\Exception\IOException;
6+
use Symfony\Component\Filesystem\Filesystem;
7+
8+
class LegacyFilesystemLock implements Lock
9+
{
10+
/**
11+
* Array key is a destination name. String.
12+
*
13+
* @var LockHandler[]
14+
*/
15+
private $lockHandlers;
16+
17+
public function __construct()
18+
{
19+
$this->lockHandlers = [];
20+
}
21+
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function lock(FsDestination $destination)
26+
{
27+
$lockHandler = $this->getLockHandler($destination);
28+
29+
if (false == $lockHandler->lock(true)) {
30+
throw new CannotObtainLockException(sprintf('Cannot obtain the lock for destination %s', $destination->getName()));
31+
}
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function release(FsDestination $destination)
38+
{
39+
$lockHandler = $this->getLockHandler($destination);
40+
41+
$lockHandler->release();
42+
}
43+
44+
public function releaseAll()
45+
{
46+
foreach ($this->lockHandlers as $lockHandler) {
47+
$lockHandler->release();
48+
}
49+
50+
$this->lockHandlers = [];
51+
}
52+
53+
/**
54+
* @param FsDestination $destination
55+
*
56+
* @return LockHandler
57+
*/
58+
private function getLockHandler(FsDestination $destination)
59+
{
60+
if (false == isset($this->lockHandlers[$destination->getName()])) {
61+
$this->lockHandlers[$destination->getName()] = new LockHandler(
62+
$destination->getName(),
63+
$destination->getFileInfo()->getPath()
64+
);
65+
}
66+
67+
return $this->lockHandlers[$destination->getName()];
68+
}
69+
}
70+
71+
// symfony/lock component works only with 3.x and 4.x Symfony
72+
// For symfony 2.x we should use LockHandler from symfony/component which was removed from 4.x
73+
// because we cannot use both at the same time. I copied and pasted the lock handler here
74+
75+
/*
76+
* This file is part of the Symfony package.
77+
*
78+
* (c) Fabien Potencier <[email protected]>
79+
*
80+
* For the full copyright and license information, please view the LICENSE
81+
* file that was distributed with this source code.
82+
*/
83+
84+
/**
85+
* LockHandler class provides a simple abstraction to lock anything by means of
86+
* a file lock.
87+
*
88+
* A locked file is created based on the lock name when calling lock(). Other
89+
* lock handlers will not be able to lock the same name until it is released
90+
* (explicitly by calling release() or implicitly when the instance holding the
91+
* lock is destroyed).
92+
*
93+
* @author Grégoire Pineau <[email protected]>
94+
* @author Romain Neutron <[email protected]>
95+
* @author Nicolas Grekas <[email protected]>
96+
*
97+
* @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Lock\Store\SemaphoreStore or Symfony\Component\Lock\Store\FlockStore instead.
98+
*/
99+
class LockHandler
100+
{
101+
private $file;
102+
private $handle;
103+
104+
/**
105+
* @param string $name The lock name
106+
* @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
107+
*
108+
* @throws IOException If the lock directory could not be created or is not writable
109+
*/
110+
public function __construct($name, $lockPath = null)
111+
{
112+
$lockPath = $lockPath ?: sys_get_temp_dir();
113+
114+
if (!is_dir($lockPath)) {
115+
$fs = new Filesystem();
116+
$fs->mkdir($lockPath);
117+
}
118+
119+
if (!is_writable($lockPath)) {
120+
throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath);
121+
}
122+
123+
$this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name));
124+
}
125+
126+
/**
127+
* Lock the resource.
128+
*
129+
* @param bool $blocking Wait until the lock is released
130+
*
131+
* @throws IOException If the lock file could not be created or opened
132+
*
133+
* @return bool Returns true if the lock was acquired, false otherwise
134+
*/
135+
public function lock($blocking = false)
136+
{
137+
if ($this->handle) {
138+
return true;
139+
}
140+
141+
$error = null;
142+
143+
// Silence error reporting
144+
set_error_handler(function ($errno, $msg) use (&$error) {
145+
$error = $msg;
146+
});
147+
148+
if (!$this->handle = fopen($this->file, 'r')) {
149+
if ($this->handle = fopen($this->file, 'x')) {
150+
chmod($this->file, 0444);
151+
} elseif (!$this->handle = fopen($this->file, 'r')) {
152+
usleep(100); // Give some time for chmod() to complete
153+
$this->handle = fopen($this->file, 'r');
154+
}
155+
}
156+
restore_error_handler();
157+
158+
if (!$this->handle) {
159+
throw new IOException($error, 0, null, $this->file);
160+
}
161+
162+
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
163+
// https://bugs.php.net/54129
164+
if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
165+
fclose($this->handle);
166+
$this->handle = null;
167+
168+
return false;
169+
}
170+
171+
return true;
172+
}
173+
174+
/**
175+
* Release the resource.
176+
*/
177+
public function release()
178+
{
179+
if ($this->handle) {
180+
flock($this->handle, LOCK_UN | LOCK_NB);
181+
fclose($this->handle);
182+
$this->handle = null;
183+
}
184+
}
185+
}

pkg/fs/Lock.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Enqueue\Fs;
4+
5+
interface Lock
6+
{
7+
/**
8+
* Returns the control If the look has been obtained
9+
* If not, should throw CannotObtainLockException exception.
10+
*
11+
* @param FsDestination $destination
12+
*
13+
* @throws CannotObtainLockException if look could not be obtained
14+
*/
15+
public function lock(FsDestination $destination);
16+
17+
/**
18+
* @param FsDestination $destination
19+
*/
20+
public function release(FsDestination $destination);
21+
22+
public function releaseAll();
23+
}

pkg/fs/Tests/FsContextTest.php

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -182,34 +182,6 @@ public function testShouldAllowPurgeMessagesFromQueue()
182182
$this->assertEmpty(file_get_contents($tmpFile));
183183
}
184184

185-
public function testShouldReleaseAllLocksOnClose()
186-
{
187-
new TempFile(sys_get_temp_dir().'/foo');
188-
new TempFile(sys_get_temp_dir().'/bar');
189-
190-
$context = new FsContext(sys_get_temp_dir(), 1, 0666);
191-
192-
$fooQueue = $context->createQueue('foo');
193-
$barQueue = $context->createTopic('bar');
194-
195-
$this->assertAttributeCount(0, 'lockHandlers', $context);
196-
197-
$context->workWithFile($fooQueue, 'r+', function () {
198-
});
199-
$context->workWithFile($barQueue, 'r+', function () {
200-
});
201-
$context->workWithFile($fooQueue, 'c+', function () {
202-
});
203-
$context->workWithFile($barQueue, 'c+', function () {
204-
});
205-
206-
$this->assertAttributeCount(2, 'lockHandlers', $context);
207-
208-
$context->close();
209-
210-
$this->assertAttributeCount(0, 'lockHandlers', $context);
211-
}
212-
213185
public function testShouldCreateFileOnFilesystemIfNotExistOnDeclareDestination()
214186
{
215187
$tmpFile = new TempFile(sys_get_temp_dir().'/'.uniqid());

0 commit comments

Comments
 (0)