8000 feature #21093 [Lock] Create a lock component (jderusse) · symfony/symfony@858af71 · GitHub
[go: up one dir, main page]

Skip to content

Commit 858af71

Browse files
committed
feature #21093 [Lock] Create a lock component (jderusse)
This PR was squashed before being merged into the 3.3-dev branch (closes #21093). Discussion ---------- [Lock] Create a lock component | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | they will | Fixed tickets | #20382 | License | MIT | Doc PR | symfony/symfony-docs#7364 This PR aim to add a new component Lock going further than the FileSystem\LockHandler by allowing remote backend (like Redis, memcache, etc) Inspired by ## Usage The simplest way to use lock is to inject an instance of a Lock in your service ```php class MyService { private $lock; public function __construct(LockInterface $lock) { $this->lock = $lock; } public function run() { $this->lock->acquire(true); // If I'm here, no exception had been raised. Lock is acquired try { // do my job } finally { $this->lock->release(); } } } ``` Configured with something like ```yaml services: app.my_service: class: AppBundle\MyService arguments: - app.lock.my_service app.lock.my_service: class: Symfony\Component\Lock\Lock factory: ['@locker', createLock] arguments: ['my_service'] ``` If you need to lock serveral resource on runtime, wou'll nneed to inject the LockFactory. ```php class MyService { private $lockFactory; public function __construct(LockFactoryInterface $lockFactory) { $this->lockFactory = $lockFactory; } public function run() { foreach ($this->items as $item) { $lock = $this->lockFactory->createLock((string) $item); try { $lock->acquire(); } catch (LockConflictedException $e) { continue; } // When I'm here, no exception had been, raised. Lock is acquired try { // do my job } finally { $lock->release(); } } } } ``` Configured with something like ```yaml services: app.my_service: class: AppBundle\MyService arguments: - '@locker' ``` This component allow you to refresh an expirable lock. This is usefull, if you run a long operation split in several small parts. If you lock with a ttl for the overall operatoin time and your process crash, the lock will block everybody for the defined TTL. But thank to the refresh method, you're able to lock for a small TTL, and refresh it between each parts. ```php class MyService { private $lock; public function __construct(LockInterface $lock) { $this->lock = $lock; } public function run() { $this->lock->acquire(true); try { do { $finished = $this->performLongTask(); // Increase the expire date by 300 more seconds $this->lock->refresh(); } while (!$finished) // do my job } finally { $this->lock->release(); } } } ``` ## Naming anc implementation choise ``` $lock->acquire() vs $lock->lock() ``` Choose to use acquire, because this component is full of `lock` Symfony\Component\Lock\Lock::Lock` raised a E_TOO_MANY_LOCK in my head. ``` $lock->acquire(false); $lock->acquire(true); vs $lock->aquire() $lock->waitAndAquire() ``` Not a big fan of flag feature and 2. But I choose to use the blocking flag to offer a simple (and common usecase) implementation ``` $lock = $factory->createLock($key); $lock->acquire(); vs $lock->aquire($key) ``` I choose to a the pool of locks implementation. It allow the user to create 2 instances and use cross lock even in the same process. ``` interface LockInterface final class Lock implements LockInterface vs final class Lock ``` I choose to use a Interface even if there is only one implementaiton to offer an extension point here # TODO ## In this PR * [x] tests * [x] add logs * [x] offer several redis connectors * [x] try other store implementation to validate the architecture/interface ## In other PR * documentation * add configuration in framework bundle * add stop watch in the debug bar * improve the combined store (takes the drift into account and elapsed time between each store) * implement other stores (memcache, ...) * use this component in session manipulation (fixes #4976) Commits ------- 018e0fc [Lock] Create a lock component
2 parents 6327b41 + 018e0fc commit 858af71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2976
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"symfony/inflector": "self.version",
5555
"symfony/intl": "self.version",
5656
"symfony/ldap": "self.version",
57+
"symfony/lock": "self.version",
5758
"symfony/monolog-bridge": "self.version",
5859
"symfony/options-resolver": "self.version",
5960
"symfony/process": "self.version",

src/Symfony/Component/Lock/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
phpunit.xml
3+
vendor/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
3.3.0
5+
-----
6+
7+
* added the component
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* Base ExceptionInterface for the Lock Component.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
interface ExceptionInterface
20+
{
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Exception;
13+
14+
/**
15+
* @author Jérémy Derussé <jeremy@derusse.com>
16+
*/
17+
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
18+
{
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* LockAcquiringException is thrown when an issue happens during the acquisition of a lock.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class LockAcquiringException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* LockConflictedException is thrown when a lock is acquired by someone else.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class LockConflictedException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* LockReleasingException is thrown when an issue happens during the release of a lock.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class LockReleasingException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* LockStorageException is thrown when an issue happens during the manipulation of a lock in a store.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class LockStorageException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* NotSupportedException is thrown when an unsupported method is called.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class NotSupportedException extends \LogicException implements ExceptionInterface
20+
{
21+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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;
13+
14+
use Psr\Log\LoggerAwareInterface;
15+
use Psr\Log\LoggerAwareTrait;
16+
use Psr\Log\NullLogger;
17+
18+
/**
19+
* Factory provides method to create locks.
20+
*
21+
* @author Jérémy Derussé <jeremy@derusse.com>
22+
*/
23+
class Factory implements LoggerAwareInterface
24+
{
25+
use LoggerAwareTrait;
26+
27+
private $store;
28+
29+
public function __construct(StoreInterface $store)
30+
{
31+
$this->store = $store;
32+
33+
$this->logger = new NullLogger();
34+
}
35+
36+
/**
37+
* Creates a lock for the given resource.
38+
*
39+
* @param string $resource The resource to lock
40+
* @param float $ttl maximum expected lock duration
41+
*
42+
* @return Lock
43+
*/
44+
public function createLock($resource, $ttl = 300.0)
45+
{
46+
$lock = new Lock(new Key($resource), $this->store, $ttl);
47+
$lock->setLogger($this->logger);
48+
49+
return $lock;
50+
}
51+
}

src/Symfony/Component/Lock/Key.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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;
13+
14+
/**
15+
* Key is a container for the state of the locks in stores.
16+
*
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
final class Key
20+
{
21+
private $resource;
22+
private $state = array();
23+
24+
/**
25+
* @param string $resource
26+
*/
27+
public function __construct($resource)
28+
{
29+
$this->resource = (string) $resource;
30+
}
31+
32+
public function __toString()
33+
{
34+
return $this->resource;
35+
}
36+
37+
/**
38+
* @param string $stateKey
39+
*
40+
* @return bool
41+
*/
42+
public function hasState($stateKey)
43+
{
44+
return isset($this->state[$stateKey]);
45+
}
46+
47+
/**
48+
* @param string $stateKey
49+
* @param mixed $state
50+
*/
51+
public function setState($stateKey, $state)
52+
{
53+
$this->state[$stateKey] = $state;
54+
}
55+
56+
/**
57+
* @param string $stateKey
58+
*/
59+
public function removeState($stateKey)
60+
{
61+
unset($this->state[$stateKey]);
62+
}
63+
64+
/**
65+
* @param $stateKey
66+
*
67+
* @return mixed
68+
*/
69+
public function getState($stateKey)
70+
{
71+
return $this->state[$stateKey];
72+
}
73+
}

src/Symfony/Component/Lock/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2016-2017 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

0 commit comments

Comments
 (0)
0