Skip to content

Commit 19e9ada

Browse files
authored
Merge pull request magento#3933 from magento-thunder/MAGETWO-98151
Fixed issue: - MAGETWO-98151: Add support ZooKeeper and flock locks
2 parents 7270d7e + abda912 commit 19e9ada

File tree

14 files changed

+1691
-8
lines changed

14 files changed

+1691
-8
lines changed

app/etc/di.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<preference for="Magento\Framework\Locale\ListsInterface" type="Magento\Framework\Locale\TranslatedLists" />
3939
<preference for="Magento\Framework\Locale\AvailableLocalesInterface" type="Magento\Framework\Locale\Deployed\Codes" />
4040
<preference for="Magento\Framework\Locale\OptionInterface" type="Magento\Framework\Locale\Deployed\Options" />
41-
<preference for="Magento\Framework\Lock\LockManagerInterface" type="Magento\Framework\Lock\Backend\Database" />
41+
<preference for="Magento\Framework\Lock\LockManagerInterface" type="Magento\Framework\Lock\Proxy" />
4242
<preference for="Magento\Framework\Api\AttributeTypeResolverInterface" type="Magento\Framework\Reflection\AttributeTypeResolver" />
4343
<preference for="Magento\Framework\Api\Search\SearchResultInterface" type="Magento\Framework\Api\Search\SearchResult" />
4444
<preference for="Magento\Framework\Api\Search\SearchCriteriaInterface" type="Magento\Framework\Api\Search\SearchCriteria"/>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Lock\Backend;
9+
10+
/**
11+
* \Magento\Framework\Lock\Backend\File test case
12+
*/
13+
class FileLockTest extends \PHPUnit\Framework\TestCase
14+
{
15+
/**
16+
* @var \Magento\Framework\Lock\Backend\FileLock
17+
*/
18+
private $model;
19+
20+
/**
21+
* @var \Magento\Framework\ObjectManagerInterface
22+
*/
23+
private $objectManager;
24+
25+
protected function setUp()
26+
{
27+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
28+
$this->model = $this->objectManager->create(
29+
\Magento\Framework\Lock\Backend\FileLock::class,
30+
['path' => '/tmp']
31+
);
32+
}
33+
34+
public function testLockAndUnlock()
35+
{
36+
$name = 'test_lock';
37+
38+
$this->assertFalse($this->model->isLocked($name));
39+
40+
$this->assertTrue($this->model->lock($name));
41+
$this->assertTrue($this->model->isLocked($name));
42+
$this->assertFalse($this->model->lock($name, 2));
43+
44+
$this->assertTrue($this->model->unlock($name));
45+
$this->assertFalse($this->model->isLocked($name));
46+
}
47+
48+
public function testUnlockWithoutExistingLock()
49+
{
50+
$name = 'test_lock';
51+
52+
$this->assertFalse($this->model->isLocked($name));
53+
$this->assertFalse($this->model->unlock($name));
54+
}
55+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Lock\Backend;
9+
10+
use Magento\Framework\Lock\Backend\Zookeeper as ZookeeperLock;
11+
use Magento\Framework\Lock\LockBackendFactory;
12+
use Magento\Framework\Config\File\ConfigFilePool;
13+
use Magento\Framework\App\DeploymentConfig\FileReader;
14+
use Magento\Framework\Stdlib\ArrayManager;
15+
16+
/**
17+
* \Magento\Framework\Lock\Backend\Zookeeper test case
18+
*/
19+
class ZookeeperTest extends \PHPUnit\Framework\TestCase
20+
{
21+
/**
22+
* @var FileReader
23+
*/
24+
private $configReader;
25+
26+
/**
27+
* @var \Magento\Framework\ObjectManagerInterface
28+
*/
29+
private $objectManager;
30+
31+
/**
32+
* @var LockBackendFactory
33+
*/
34+
private $lockBackendFactory;
35+
36+
/**
37+
* @var ArrayManager
38+
*/
39+
private $arrayManager;
40+
41+
/**
42+
* @var ZookeeperLock
43+
*/
44+
private $model;
45+
46+
/**
47+
* @inheritdoc
48+
*/
49+
protected function setUp()
50+
{
51+
if (!extension_loaded('zookeeper')) {
52+
$this->markTestSkipped('php extension Zookeeper is not installed.');
53+
}
54+
55+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
56+
$this->configReader = $this->objectManager->get(FileReader::class);
57+
$this->lockBackendFactory = $this->objectManager->create(LockBackendFactory::class);
58+
$this->arrayManager = $this->objectManager->create(ArrayManager::class);
59+
$config = $this->configReader->load(ConfigFilePool::APP_ENV);
60+
61+
if ($this->arrayManager->get('lock/provider', $config) !== 'zookeeper') {
62+
$this->markTestSkipped('Zookeeper is not configured during installation.');
63+
}
64+
65+
$this->model = $this->lockBackendFactory->create();
66+
$this->assertInstanceOf(ZookeeperLock::class, $this->model);
67+
}
68+
69+
public function testLockAndUnlock()
70+
{
71+
$name = 'test_lock';
72+
73+
$this->assertFalse($this->model->isLocked($name));
74+
75+
$this->assertTrue($this->model->lock($name));
76+
$this->assertTrue($this->model->isLocked($name));
77+
$this->assertFalse($this->model->lock($name, 2));
78+
79+
$this->assertTrue($this->model->unlock($name));
80+
$this->assertFalse($this->model->isLocked($name));
81+
}
82+
83+
public function testUnlockWithoutExistingLock()
84+
{
85+
$name = 'test_lock';
86+
87+
$this->assertFalse($this->model->isLocked($name));
88+
$this->assertFalse($this->model->unlock($name));
89+
}
90+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Lock\Backend;
9+
10+
use Magento\Framework\Lock\LockManagerInterface;
11+
use Magento\Framework\Filesystem\Driver\File as FileDriver;
12+
use Magento\Framework\Exception\RuntimeException;
13+
use Magento\Framework\Exception\FileSystemException;
14+
use Magento\Framework\Phrase;
15+
16+
/**
17+
* LockManager using the file system for locks
18+
*/
19+
class FileLock implements LockManagerInterface
20+
{
21+
/**
22+
* The file driver instance
23+
*
24+
* @var FileDriver
25+
*/
26+
private $fileDriver;
27+
28+
/**
29+
* The path to the locks storage folder
30+
*
31+
* @var string
32+
*/
33+
private $path;
34+
35+
/**
36+
* How many microseconds to wait before re-try to acquire a lock
37+
*
38+
* @var int
39+
*/
40+
private $sleepCycle = 100000;
41+
42+
/**
43+
* The mapping list of the path lock with the file resource
44+
*
45+
* @var array
46+
*/
47+
private $locks = [];
48+
49+
/**
50+
* @param FileDriver $fileDriver The file driver
51+
* @param string $path The path to the locks storage folder
52+
* @throws RuntimeException Throws RuntimeException if $path is empty
53+
* or cannot create the directory for locks
54+
*/
55+
public function __construct(FileDriver $fileDriver, string $path)
56+
{
57+
if (!$path) {
58+
throw new RuntimeException(new Phrase('The path needs to be a non-empty string.'));
59+
}
60+
61+
$this->fileDriver = $fileDriver;
62+
$this->path = rtrim($path, '/') . '/';
63+
64+
try {
65+
if (!$this->fileDriver->isExists($this->path)) {
66+
$this->fileDriver->createDirectory($this->path);
67+
}
68+
} catch (FileSystemException $exception) {
69+
throw new RuntimeException(
70+
new Phrase('Cannot create the directory for locks: %1', [$this->path]),
71+
$exception
72+
);
73+
}
74+
}
75+
76+
/**
77+
* Acquires a lock by name
78+
*
79+
* @param string $name The lock name
80+
* @param int $timeout Timeout in seconds. A negative timeout value means infinite timeout
81+
* @return bool Returns true if the lock is acquired, otherwise returns false
82+
* @throws RuntimeException Throws RuntimeException if cannot acquires the lock because FS problems
83+
*/
84+
public function lock(string $name, int $timeout = -1): bool
85+
{
86+
try {
87+
$lockFile = $this->getLockPath($name);
88+
$fileResource = $this->fileDriver->fileOpen($lockFile, 'w+');
89+
$skipDeadline = $timeout < 0;
90+
$deadline = microtime(true) + $timeout;
91+
92+
while (!$this->tryToLock($fileResource)) {
93+
if (!$skipDeadline && $deadline <= microtime(true)) {
94+
$this->fileDriver->fileClose($fileResource);
95+
return false;
96+
}
97+
usleep($this->sleepCycle);
98+
}
99+
} catch (FileSystemException $exception) {
100+
throw new RuntimeException(new Phrase('Cannot acquire a lock.'), $exception);
101+
}
102+
103+
$this->locks[$lockFile] = $fileResource;
104+
return true;
105+
}
106+
107+
/**
108+
* Checks if a lock exists by name
109+
*
110+
* @param string $name The lock name
111+
* @return bool Returns true if the lock exists, otherwise returns false
112+
* @throws RuntimeException Throws RuntimeException if cannot check that the lock exists
113+
*/
114+
public function isLocked(string $name): bool
115+
{
116+
$lockFile = $this->getLockPath($name);
117+
$result = false;
118+
119+
try {
120+
if ($this->fileDriver->isExists($lockFile)) {
121+
$fileResource = $this->fileDriver->fileOpen($lockFile, 'w+');
122+
if ($this->tryToLock($fileResource)) {
123+
$result = false;
124+
} else {
125+
$result = true;
126+
}
127+
$this->fileDriver->fileClose($fileResource);
128+
}
129+
} catch (FileSystemException $exception) {
130+
throw new RuntimeException(new Phrase('Cannot verify that the lock exists.'), $exception);
131+
}
132+
133+
return $result;
134+
}
135+
136+
/**
137+
* Remove the lock by name
138+
*
139+
* @param string $name The lock name
140+
* @return bool If the lock is removed returns true, otherwise returns false
141+
*/
142+
public function unlock(string $name): bool
143+
{
144+
$lockFile = $this->getLockPath($name);
145+
146+
if (isset($this->locks[$lockFile]) && $this->tryToUnlock($this->locks[$lockFile])) {
147+
unset($this->locks[$lockFile]);
148+
return true;
149+
}
150+
151+
return false;
152+
}
153+
154+
/**
155+
* Returns the full path to the lock file by name
156+
*
157+
* @param string $name The lock name
158+
* @return string The path to the lock file
159+
*/
160+
private function getLockPath(string $name): string
161+
{
162+
return $this->path . $name;
163+
}
164+
165+
/**
166+
* Tries to lock a file resource
167+
*
168+
* @param resource $resource The file resource
169+
* @return bool If the lock is acquired returns true, otherwise returns false
170+
*/
171+
private function tryToLock($resource): bool
172+
{
173+
try {
174+
return $this->fileDriver->fileLock($resource, LOCK_EX | LOCK_NB);
175+
} catch (FileSystemException $exception) {
176+
return false;
177+
}
178+
}
179+
180+
/**
181+
* Tries to unlock a file resource
182+
*
183+
* @param resource $resource The file resource
184+
* @return bool If the lock is removed returns true, otherwise returns false
185+
*/
186+
private function tryToUnlock($resource): bool
187+
{
188+
try {
189+
return $this->fileDriver->fileLock($resource, LOCK_UN | LOCK_NB);
190+
} catch (FileSystemException $exception) {
191+
return false;
192+
}
193+
}
194+
}

0 commit comments

Comments
 (0)