Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f934b95

Browse filesBrowse files
committed
[Lock] Add MysqlStore that use GET_LOCK
* Key is hashed with sha256 to ensure it stays between 1 and 64 characters * Create a new PDO connection for each lock, to avoid multiple locks on the same name in the same session
1 parent 07766b3 commit f934b95
Copy full SHA for f934b95

File tree

4 files changed

+170
-0
lines changed
Filter options

4 files changed

+170
-0
lines changed

‎src/Symfony/Component/Lock/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.1.0
5+
-----
6+
7+
* added Mysql store using GET_LOCK
8+
49
3.4.0
510
-----
611

+128Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock\Store;
13+
14+
use Symfony\Component\Lock\Exception\LockConflictedException;
15+
use Symfony\Component\Lock\Key;
16+
use Symfony\Component\Lock\StoreInterface;
17+
18+
/**
19+
* MysqlStore is a StoreInterface implementation using MySQL/MariaDB GET_LOCK function
20+
*
21+
* @author Jérôme TAMARELLE <jerome@tamarelle.net>
22+
*/
23+
class MysqlStore implements StoreInterface
24+
{
25+
private $dsn;
26+
private $username;
27+
private $password;
28+
private $options;
29+
30+
private $waitTimeout;
31+
32+
/**
33+
* @param array $connection
34+
* - db_dsn:
35+
* - db_username:
36+
* - db_password:
37+
* - db_options:
38+
* - timeout: Time in seconds to wait for a lock to be released.
39+
* A negative timeout value means infinite timeout.
40+
*/
41+
public function __construct(array $options)
42+
{
43+
$this->dsn = $options['dsn'] ?? 'mysql:host=localhost';
44+
$this->username = $options['db_username'] ?? 'root';
45+
$this->password = $options['db_password'] ?? null;
46+
$this->options = $options['db_options'] ?? array();
47+
$this->waitTimeout = $options['timeout'] ?? -1;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function save(Key $key)
54+
{
55+
$this->lock($key, false);
56+
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*/
61+
public function waitAndSave(Key $key)
62+
{
63+
$this->lock($key, true);
64+
}
65+
66+
private function lock(Key $key, bool $blocking)
67+
{
68+
// The lock is maybe already acquired.
69+
if ($key->hasState(__CLASS__)) {
70+
return;
71+
}
72+
73+
// no timeout for impatient
74+
$timeout = $blocking ? $this->waitTimeout : 0;
75+
76+
$connection = new \PDO($this->dsn, $this->username, $this->password, $this->options);
77+
$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
78+
79+
$stmt = $connection->prepare('SELECT GET_LOCK(:key, :timeout)');
80+
$stmt->bindValue(':key', hash('sha256', $key), \PDO::PARAM_STR);
81+
$stmt->bindValue(':timeout', $timeout, \PDO::PARAM_INT);
82+
$stmt->setFetchMode(\PDO::FETCH_COLUMN, 0);
83+
$stmt->execute();
84+
$success = $stmt->fetchColumn();
85+
86+
if ('0' === $success) {
87+
throw new LockConflictedException();
88+
}
89+
90+
// store the release statement in the state
91+
$releaseStmt = $connection->prepare('SELECT RELEASE_LOCK(:key)');
92+
$releaseStmt->bindValue(':key', hash('sha256', $key), \PDO::PARAM_STR);
93+
94+
$key->setState(__CLASS__, $releaseStmt);
95+
}
96+
97+
/**
98+
* {@inheritdoc}
99+
*/
100+
public function putOffExpiration(Key $key, $ttl)
101+
{
102+
// do nothing, the GET_LOCK locks forever, until the session terminates.
103+
}
104+
105+
/**
106+
* {@inheritdoc}
107+
*/
108+
public function delete(Key $key)
109+
{
110+
if (!$key->hasState(__CLASS__)) {
111+
return;
112+
}
113+
114+
$releaseStmt = $key->getState(__CLASS__);
115+
$releaseStmt->execute();
116+
117+
// Close the connection.
118+
$key->removeState(__CLASS__);
119+
}
120+
121+
/**
122+
* {@inheritdoc}
123+
*/
124+
public function exists(Key $key)
125+
{
126+
return $key->hasState(__CLASS__);
127+
}
128+
}
+34Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock\Tests\Store;
13+
14+
use Symfony\Component\Lock\Store\MysqlStore;
15+
16+
/**
17+
* @author Jérôme TAMARELLE <jerome@tamarelle.net>
18+
*/
19+
class MysqlStoreTest extends AbstractStoreTest
20+
{
21+
use BlockingStoreTestTrait;
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function getStore()
27+
{
28+
return new MysqlStore(array(
29+
'db_dsn' => getenv('MYSQL_DSN'),
30+
'db_username' => getenv('MYSQL_USER'),
31+
'db_password' => getenv('MYSQL_PASSWORD'),
32+
));
33+
}
34+
}

‎src/Symfony/Component/Lock/phpunit.xml.dist

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/phpunit.xml.dist
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
<ini name="error_reporting" value="-1" />
1313
<env name="REDIS_HOST" value="localhost" />
1414
<env name="MEMCACHED_HOST" value="localhost" />
15+
<env name="MYSQL_DSN" value="mysql:host=localhost" />
16+
<env name="MYSQL_USER" value="root" />
17+
<env name="MYSQL_PASSWORD" value="" />
1518
</php>
1619

1720
<testsuites>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.