Skip to content

Commit

Permalink
Merge pull request magento#3933 from magento-thunder/MAGETWO-98151
Browse files Browse the repository at this point in the history
Fixed issue:
 - MAGETWO-98151: Add support ZooKeeper and flock locks
  • Loading branch information
arhiopterecs authored Mar 22, 2019
2 parents 7270d7e + abda912 commit 19e9ada
Show file tree
Hide file tree
Showing 14 changed files with 1,691 additions and 8 deletions.
2 changes: 1 addition & 1 deletion app/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<preference for="Magento\Framework\Locale\ListsInterface" type="Magento\Framework\Locale\TranslatedLists" />
<preference for="Magento\Framework\Locale\AvailableLocalesInterface" type="Magento\Framework\Locale\Deployed\Codes" />
<preference for="Magento\Framework\Locale\OptionInterface" type="Magento\Framework\Locale\Deployed\Options" />
<preference for="Magento\Framework\Lock\LockManagerInterface" type="Magento\Framework\Lock\Backend\Database" />
<preference for="Magento\Framework\Lock\LockManagerInterface" type="Magento\Framework\Lock\Proxy" />
<preference for="Magento\Framework\Api\AttributeTypeResolverInterface" type="Magento\Framework\Reflection\AttributeTypeResolver" />
<preference for="Magento\Framework\Api\Search\SearchResultInterface" type="Magento\Framework\Api\Search\SearchResult" />
<preference for="Magento\Framework\Api\Search\SearchCriteriaInterface" type="Magento\Framework\Api\Search\SearchCriteria"/>
Expand Down
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));
}
}
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 lib/internal/Magento/Framework/Lock/Backend/FileLock.php
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;
}
}
}
Loading

0 comments on commit 19e9ada

Please sign in to comment.