Skip to content

Commit d3ad3c5

Browse files
paveqBohdan Korablov
authored andcommitted
MAGETWO-94369: [Forwardport] Move cron improvements from 2.2 to 2.3
1 parent 3ecc23f commit d3ad3c5

File tree

5 files changed

+353
-0
lines changed

5 files changed

+353
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/**
8+
* \Magento\Framework\Lock\Backend\Database test case
9+
*/
10+
namespace Magento\Framework\Lock\Backend;
11+
12+
class DatabaseTest extends \PHPUnit\Framework\TestCase
13+
{
14+
/**
15+
* @var \Magento\Framework\Lock\Backend\Database
16+
*/
17+
private $model;
18+
19+
/**
20+
* @var \Magento\Framework\ObjectManagerInterface
21+
*/
22+
private $objectManager;
23+
24+
protected function setUp()
25+
{
26+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
27+
$this->model = $this->objectManager->create(\Magento\Framework\Lock\Backend\Database::class);
28+
}
29+
30+
public function testLockAndUnlock()
31+
{
32+
$name = 'test_lock';
33+
34+
$this->assertFalse($this->model->isLocked($name));
35+
36+
$this->assertTrue($this->model->lock($name));
37+
$this->assertTrue($this->model->isLocked($name));
38+
39+
$this->assertTrue($this->model->unlock($name));
40+
$this->assertFalse($this->model->isLocked($name));
41+
}
42+
43+
public function testUnlockWithoutExistingLock()
44+
{
45+
$name = 'test_lock';
46+
47+
$this->assertFalse($this->model->isLocked($name));
48+
$this->assertFalse($this->model->unlock($name));
49+
}
50+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
namespace Magento\Framework\Lock\Backend;
9+
10+
use Magento\Framework\App\DeploymentConfig;
11+
use Magento\Framework\App\ResourceConnection;
12+
use Magento\Framework\Config\ConfigOptionsListConstants;
13+
use Magento\Framework\Exception\AlreadyExistsException;
14+
use Magento\Framework\Exception\InputException;
15+
use Magento\Framework\Phrase;
16+
17+
class Database implements \Magento\Framework\Lock\LockManagerInterface
18+
{
19+
/** @var ResourceConnection */
20+
private $resource;
21+
22+
/** @var DeploymentConfig */
23+
private $deploymentConfig;
24+
25+
/** @var string Lock prefix */
26+
private $prefix;
27+
28+
/** @var string|false Holds current lock name if set, otherwise false */
29+
private $currentLock = false;
30+
31+
public function __construct(
32+
ResourceConnection $resource,
33+
DeploymentConfig $deploymentConfig,
34+
string $prefix = null
35+
) {
36+
$this->resource = $resource;
37+
$this->deploymentConfig = $deploymentConfig;
38+
$this->prefix = $prefix;
39+
}
40+
41+
/**
42+
* Sets a lock for name
43+
*
44+
* @param string $name lock name
45+
* @param int $timeout How long to wait lock acquisition in seconds, negative value means infinite timeout
46+
* @return bool
47+
* @throws InputException
48+
* @throws AlreadyExistsException
49+
*/
50+
public function lock(string $name, int $timeout = -1): bool
51+
{
52+
$name = $this->addPrefix($name);
53+
54+
/**
55+
* Before MySQL 5.7.5, only a single simultaneous lock per connection can be acquired.
56+
* This limitation can be removed once MySQL minimum requirement has been raised,
57+
* currently we support MySQL 5.6 way only.
58+
*/
59+
if ($this->currentLock) {
60+
throw new AlreadyExistsException(
61+
new Phrase(
62+
'Current connection is already holding lock for $1, only single lock allowed',
63+
[$this->currentLock]
64+
)
65+
);
66+
}
67+
68+
$result = (bool)$this->resource->getConnection()->query(
69+
"SELECT GET_LOCK(?, ?);",
70+
[(string)$name, (int)$timeout]
71+
)->fetchColumn();
72+
73+
if ($result === true) {
74+
$this->currentLock = $name;
75+
}
76+
77+
return $result;
78+
}
79+
80+
/**
81+
* Releases a lock for name
82+
*
83+
* @param string $name lock name
84+
* @return bool
85+
* @throws InputException
86+
*/
87+
public function unlock(string $name): bool
88+
{
89+
$name = $this->addPrefix($name);
90+
91+
$result = (bool)$this->resource->getConnection()->query(
92+
"SELECT RELEASE_LOCK(?);",
93+
[(string)$name]
94+
)->fetchColumn();
95+
96+
if ($result === true) {
97+
$this->currentLock = false;
98+
}
99+
100+
return $result;
101+
}
102+
103+
/**
104+
* Tests of lock is set for name
105+
*
106+
* @param string $name lock name
107+
* @return bool
108+
* @throws InputException
109+
*/
110+
public function isLocked(string $name): bool
111+
{
112+
$name = $this->addPrefix($name);
113+
114+
return (bool)$this->resource->getConnection()->query(
115+
"SELECT IS_USED_LOCK(?);",
116+
[(string)$name]
117+
)->fetchColumn();
118+
}
119+
120+
/**
121+
* Adds prefix and checks for max length of lock name
122+
*
123+
* Limited to 64 characters in MySQL.
124+
*
125+
* @param string $name
126+
* @return string $name
127+
* @throws InputException
128+
*/
129+
private function addPrefix(string $name): string
130+
{
131+
$name = $this->getPrefix() . '|' . $name;
132+
133+
if (strlen($name) > 64) {
134+
throw new InputException(new Phrase('Lock name too long: %1...', [substr($name, 0, 64)]));
135+
}
136+
137+
return $name;
138+
}
139+
140+
/**
141+
* Get installation specific lock prefix to avoid lock conflicts
142+
*
143+
* @return string lock prefix
144+
*/
145+
private function getPrefix(): string
146+
{
147+
if ($this->prefix === null) {
148+
$this->prefix = (string)$this->deploymentConfig->get(
149+
ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT
150+
. '/'
151+
. ConfigOptionsListConstants::KEY_NAME,
152+
''
153+
);
154+
}
155+
156+
return $this->prefix;
157+
}
158+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
namespace Magento\Framework\Lock;
9+
10+
/**
11+
* Interface of a lock manager
12+
*
13+
* @api
14+
*/
15+
interface LockManagerInterface
16+
{
17+
/**
18+
* Sets a lock
19+
*
20+
* @param string $name lock name
21+
* @param int $timeout How long to wait lock acquisition in seconds, negative value means infinite timeout
22+
* @return bool
23+
* @api
24+
*/
25+
public function lock(string $name, int $timeout = -1): bool;
26+
27+
/**
28+
* Releases a lock
29+
*
30+
* @param string $name lock name
31+
* @return bool
32+
* @api
33+
*/
34+
public function unlock(string $name): bool;
35+
36+
/**
37+
* Tests if lock is set
38+
*
39+
* @param string $name lock name
40+
* @return bool
41+
* @api
42+
*/
43+
public function isLocked(string $name): bool;
44+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Lock library
2+
3+
Lock library provides mechanism to acquire Magento system-wide lock. Default implementation is based on MySQL locks, where any locks are automatically released on connection close.
4+
5+
The library provides interface *LockManagerInterface* which provides following methods:
6+
* *lock* - Acquires a named lock
7+
* *unlock* - Releases a named lock
8+
* *isLocked* - Tests if a named lock exists
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Lock\Test\Unit\Backend;
7+
8+
use Magento\Framework\Lock\Backend\Database;
9+
10+
class DatabaseTest extends \PHPUnit\Framework\TestCase
11+
{
12+
/**
13+
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResourceConnection
14+
*/
15+
private $resource;
16+
17+
/**
18+
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\DB\Adapter\AdapterInterface
19+
*/
20+
private $connection;
21+
22+
/**
23+
* @var \PHPUnit_Framework_MockObject_MockObject|\Zend_Db_Statement_Interface
24+
*/
25+
private $statement;
26+
27+
/**
28+
* @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
29+
*/
30+
private $objectManager;
31+
32+
/** @var Database $database */
33+
private $database;
34+
35+
protected function setUp()
36+
{
37+
$this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
38+
->disableOriginalConstructor()
39+
->getMockForAbstractClass();
40+
$this->resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class)
41+
->disableOriginalConstructor()
42+
->getMock();
43+
$this->statement = $this->getMockBuilder(\Zend_Db_Statement_Interface::class)
44+
->disableOriginalConstructor()
45+
->getMockForAbstractClass();
46+
47+
$this->resource->expects($this->any())
48+
->method('getConnection')
49+
->willReturn($this->connection);
50+
51+
$this->connection->expects($this->any())
52+
->method('query')
53+
->willReturn($this->statement);
54+
55+
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
56+
57+
/** @var Database $database */
58+
$this->database = $this->objectManager->getObject(
59+
Database::class,
60+
['resource' => $this->resource]
61+
);
62+
}
63+
64+
public function testLock()
65+
{
66+
$this->statement->expects($this->once())
67+
->method('fetchColumn')
68+
->willReturn(true);
69+
70+
$this->assertTrue($this->database->lock('testLock'));
71+
}
72+
73+
/**
74+
* @expectedException \Magento\Framework\Exception\InputException
75+
*/
76+
public function testlockWithTooLongName()
77+
{
78+
$this->database->lock('BbXbyf9rIY5xuAVdviQJmh76FyoeeVHTDpcjmcImNtgpO4Hnz4xk76ZGEyYALvrQu');
79+
}
80+
81+
/**
82+
* @expectedException \Magento\Framework\Exception\AlreadyExistsException
83+
*/
84+
public function testlockWithAlreadyAcquiredLockInSameSession()
85+
{
86+
$this->statement->expects($this->any())
87+
->method('fetchColumn')
88+
->willReturn(true);
89+
90+
$this->database->lock('testLock');
91+
$this->database->lock('differentLock');
92+
}
93+
}

0 commit comments

Comments
 (0)