forked from magento/magento2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request magento#3933 from magento-thunder/MAGETWO-98151
Fixed issue: - MAGETWO-98151: Add support ZooKeeper and flock locks
- Loading branch information
Showing
14 changed files
with
1,691 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Framework\Lock\Backend; | ||
|
||
/** | ||
* \Magento\Framework\Lock\Backend\File test case | ||
*/ | ||
class FileLockTest extends \PHPUnit\Framework\TestCase | ||
{ | ||
/** | ||
* @var \Magento\Framework\Lock\Backend\FileLock | ||
*/ | ||
private $model; | ||
|
||
/** | ||
* @var \Magento\Framework\ObjectManagerInterface | ||
*/ | ||
private $objectManager; | ||
|
||
protected function setUp() | ||
{ | ||
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); | ||
$this->model = $this->objectManager->create( | ||
\Magento\Framework\Lock\Backend\FileLock::class, | ||
['path' => '/tmp'] | ||
); | ||
} | ||
|
||
public function testLockAndUnlock() | ||
{ | ||
$name = 'test_lock'; | ||
|
||
$this->assertFalse($this->model->isLocked($name)); | ||
|
||
$this->assertTrue($this->model->lock($name)); | ||
$this->assertTrue($this->model->isLocked($name)); | ||
$this->assertFalse($this->model->lock($name, 2)); | ||
|
||
$this->assertTrue($this->model->unlock($name)); | ||
$this->assertFalse($this->model->isLocked($name)); | ||
} | ||
|
||
public function testUnlockWithoutExistingLock() | ||
{ | ||
$name = 'test_lock'; | ||
|
||
$this->assertFalse($this->model->isLocked($name)); | ||
$this->assertFalse($this->model->unlock($name)); | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/ZookeeperTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Framework\Lock\Backend; | ||
|
||
use Magento\Framework\Lock\Backend\Zookeeper as ZookeeperLock; | ||
use Magento\Framework\Lock\LockBackendFactory; | ||
use Magento\Framework\Config\File\ConfigFilePool; | ||
use Magento\Framework\App\DeploymentConfig\FileReader; | ||
use Magento\Framework\Stdlib\ArrayManager; | ||
|
||
/** | ||
* \Magento\Framework\Lock\Backend\Zookeeper test case | ||
*/ | ||
class ZookeeperTest extends \PHPUnit\Framework\TestCase | ||
{ | ||
/** | ||
* @var FileReader | ||
*/ | ||
private $configReader; | ||
|
||
/** | ||
* @var \Magento\Framework\ObjectManagerInterface | ||
*/ | ||
private $objectManager; | ||
|
||
/** | ||
* @var LockBackendFactory | ||
*/ | ||
private $lockBackendFactory; | ||
|
||
/** | ||
* @var ArrayManager | ||
*/ | ||
private $arrayManager; | ||
|
||
/** | ||
* @var ZookeeperLock | ||
*/ | ||
private $model; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
protected function setUp() | ||
{ | ||
if (!extension_loaded('zookeeper')) { | ||
$this->markTestSkipped('php extension Zookeeper is not installed.'); | ||
} | ||
|
||
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); | ||
$this->configReader = $this->objectManager->get(FileReader::class); | ||
$this->lockBackendFactory = $this->objectManager->create(LockBackendFactory::class); | ||
$this->arrayManager = $this->objectManager->create(ArrayManager::class); | ||
$config = $this->configReader->load(ConfigFilePool::APP_ENV); | ||
|
||
if ($this->arrayManager->get('lock/provider', $config) !== 'zookeeper') { | ||
$this->markTestSkipped('Zookeeper is not configured during installation.'); | ||
} | ||
|
||
$this->model = $this->lockBackendFactory->create(); | ||
$this->assertInstanceOf(ZookeeperLock::class, $this->model); | ||
} | ||
|
||
public function testLockAndUnlock() | ||
{ | ||
$name = 'test_lock'; | ||
|
||
$this->assertFalse($this->model->isLocked($name)); | ||
|
||
$this->assertTrue($this->model->lock($name)); | ||
$this->assertTrue($this->model->isLocked($name)); | ||
$this->assertFalse($this->model->lock($name, 2)); | ||
|
||
$this->assertTrue($this->model->unlock($name)); | ||
$this->assertFalse($this->model->isLocked($name)); | ||
} | ||
|
||
public function testUnlockWithoutExistingLock() | ||
{ | ||
$name = 'test_lock'; | ||
|
||
$this->assertFalse($this->model->isLocked($name)); | ||
$this->assertFalse($this->model->unlock($name)); | ||
} | ||
} |
194 changes: 194 additions & 0 deletions
194
lib/internal/Magento/Framework/Lock/Backend/FileLock.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Framework\Lock\Backend; | ||
|
||
use Magento\Framework\Lock\LockManagerInterface; | ||
use Magento\Framework\Filesystem\Driver\File as FileDriver; | ||
use Magento\Framework\Exception\RuntimeException; | ||
use Magento\Framework\Exception\FileSystemException; | ||
use Magento\Framework\Phrase; | ||
|
||
/** | ||
* LockManager using the file system for locks | ||
*/ | ||
class FileLock implements LockManagerInterface | ||
{ | ||
/** | ||
* The file driver instance | ||
* | ||
* @var FileDriver | ||
*/ | ||
private $fileDriver; | ||
|
||
/** | ||
* The path to the locks storage folder | ||
* | ||
* @var string | ||
*/ | ||
private $path; | ||
|
||
/** | ||
* How many microseconds to wait before re-try to acquire a lock | ||
* | ||
* @var int | ||
*/ | ||
private $sleepCycle = 100000; | ||
|
||
/** | ||
* The mapping list of the path lock with the file resource | ||
* | ||
* @var array | ||
*/ | ||
private $locks = []; | ||
|
||
/** | ||
* @param FileDriver $fileDriver The file driver | ||
* @param string $path The path to the locks storage folder | ||
* @throws RuntimeException Throws RuntimeException if $path is empty | ||
* or cannot create the directory for locks | ||
*/ | ||
public function __construct(FileDriver $fileDriver, string $path) | ||
{ | ||
if (!$path) { | ||
throw new RuntimeException(new Phrase('The path needs to be a non-empty string.')); | ||
} | ||
|
||
$this->fileDriver = $fileDriver; | ||
$this->path = rtrim($path, '/') . '/'; | ||
|
||
try { | ||
if (!$this->fileDriver->isExists($this->path)) { | ||
$this->fileDriver->createDirectory($this->path); | ||
} | ||
} catch (FileSystemException $exception) { | ||
throw new RuntimeException( | ||
new Phrase('Cannot create the directory for locks: %1', [$this->path]), | ||
$exception | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Acquires a lock by name | ||
* | ||
* @param string $name The lock name | ||
* @param int $timeout Timeout in seconds. A negative timeout value means infinite timeout | ||
* @return bool Returns true if the lock is acquired, otherwise returns false | ||
* @throws RuntimeException Throws RuntimeException if cannot acquires the lock because FS problems | ||
*/ | ||
public function lock(string $name, int $timeout = -1): bool | ||
{ | ||
try { | ||
$lockFile = $this->getLockPath($name); | ||
$fileResource = $this->fileDriver->fileOpen($lockFile, 'w+'); | ||
$skipDeadline = $timeout < 0; | ||
$deadline = microtime(true) + $timeout; | ||
|
||
while (!$this->tryToLock($fileResource)) { | ||
if (!$skipDeadline && $deadline <= microtime(true)) { | ||
$this->fileDriver->fileClose($fileResource); | ||
return false; | ||
} | ||
usleep($this->sleepCycle); | ||
} | ||
} catch (FileSystemException $exception) { | ||
throw new RuntimeException(new Phrase('Cannot acquire a lock.'), $exception); | ||
} | ||
|
||
$this->locks[$lockFile] = $fileResource; | ||
return true; | ||
} | ||
|
||
/** | ||
* Checks if a lock exists by name | ||
* | ||
* @param string $name The lock name | ||
* @return bool Returns true if the lock exists, otherwise returns false | ||
* @throws RuntimeException Throws RuntimeException if cannot check that the lock exists | ||
*/ | ||
public function isLocked(string $name): bool | ||
{ | ||
$lockFile = $this->getLockPath($name); | ||
$result = false; | ||
|
||
try { | ||
if ($this->fileDriver->isExists($lockFile)) { | ||
$fileResource = $this->fileDriver->fileOpen($lockFile, 'w+'); | ||
if ($this->tryToLock($fileResource)) { | ||
$result = false; | ||
} else { | ||
$result = true; | ||
} | ||
$this->fileDriver->fileClose($fileResource); | ||
} | ||
} catch (FileSystemException $exception) { | ||
throw new RuntimeException(new Phrase('Cannot verify that the lock exists.'), $exception); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Remove the lock by name | ||
* | ||
* @param string $name The lock name | ||
* @return bool If the lock is removed returns true, otherwise returns false | ||
*/ | ||
public function unlock(string $name): bool | ||
{ | ||
$lockFile = $this->getLockPath($name); | ||
|
||
if (isset($this->locks[$lockFile]) && $this->tryToUnlock($this->locks[$lockFile])) { | ||
unset($this->locks[$lockFile]); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Returns the full path to the lock file by name | ||
* | ||
* @param string $name The lock name | ||
* @return string The path to the lock file | ||
*/ | ||
private function getLockPath(string $name): string | ||
{ | ||
return $this->path . $name; | ||
} | ||
|
||
/** | ||
* Tries to lock a file resource | ||
* | ||
* @param resource $resource The file resource | ||
* @return bool If the lock is acquired returns true, otherwise returns false | ||
*/ | ||
private function tryToLock($resource): bool | ||
{ | ||
try { | ||
return $this->fileDriver->fileLock($resource, LOCK_EX | LOCK_NB); | ||
} catch (FileSystemException $exception) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Tries to unlock a file resource | ||
* | ||
* @param resource $resource The file resource | ||
* @return bool If the lock is removed returns true, otherwise returns false | ||
*/ | ||
private function tryToUnlock($resource): bool | ||
{ | ||
try { | ||
return $this->fileDriver->fileLock($resource, LOCK_UN | LOCK_NB); | ||
} catch (FileSystemException $exception) { | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.