-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Lock] Create a lock component #21093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
When I worked on the Filesystem/LockHandler we (fab, niko, me) decided to not implement a "lock component" because it was always very specific to each use case: one vs multiple server. lock in redis with a ttl, Redlock, etc. May be it's time to reconsider this and to introduce in symfony a new lock component. Anyway, about the end user API: I saw you arguments about More over, I would prefer a system based on |
Since you said the component is already full of Lock names, what about call the component |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor points, architectural I think it looks fine. I'm not too experienced with locking itself, but this component is similar an in-house Mutex component my company wrote, thus I do like it a lot.
src/Symfony/Component/Lock/Lock.php
Outdated
throw $e; | ||
} catch (\Exception $e) { | ||
throw new LockAcquiringException('', 0, $e); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you be catching \Throwable
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it make sens to wrap every exception with "LockException" here?
Or I should just let the original exception been thrown.
If yes, you're right, I should catch everything.
If no, I can remove this statement.
WDYT?
src/Symfony/Component/Lock/Lock.php
Outdated
throw $e; | ||
} catch (\Exception $e) { | ||
throw new LockAcquiringException('', 0, $e); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you be catching \Throwable
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on Error, it should let the error throw imho. throwing a LockAcquiringException would make no sense. Error is a dev mistake, not a runtime one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I wanted to make sure this was considered
src/Symfony/Component/Lock/Lock.php
Outdated
} else { | ||
throw new InvalidArgumentException( | ||
sprintf('Unable to acquire a blocking lock on a instance of "%s".', get_class($this->store)) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should go on 1 line
* | ||
* @author Jérémy Derussé <jeremy@derusse.com> | ||
*/ | ||
class CombinedStore implements StoreInterface, ExpirableStoreInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be made final
* | ||
* @author Jérémy Derussé <jeremy@derusse.com> | ||
*/ | ||
class UnanimousQuorum implements QuorumInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be made final
src/Symfony/Component/Lock/Lock.php
Outdated
use Symfony\Component\Lock\Store\ExpirableStoreInterface; | ||
|
||
/** | ||
* Lock is the default implementation to the LockInterface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implementation to of the LockInterface
{ | ||
/** | ||
* Acquire the lock. If the lock is acquired by someone else, the parameter `blocking` determine whether or not | ||
* the the call should block until the release of the lock. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the parameter blocking
determines whether
* Acquire the lock. If the lock is acquired by someone else, the parameter `blocking` determine whether or not | ||
* the the call should block until the release of the lock. | ||
* | ||
* @param bool $blocking whether or not the Lock should wait the release of someone else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should wait for the release of
* Returns Whether or not the quorum *could* be met. | ||
* This method does not means the quorum *would* be met for sur. But can be useful to early stop a process when you | ||
* known there isn't any chance to met the quorum. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a blank line between the main and sub description
This method does not means the quorum *would* be met for sur. But sure, but can be useful to early stop a process early when you known there isn't any is no chance to meet the quorum.
interface BlockingStoreInterface | ||
{ | ||
/** | ||
* Wait a key became free then stores the resource. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait a key became becomes free**,** then stores the resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree on the finals. Im not into locking myself as well.. at first it looked slightly over-engineered.. then again i could understand we're super flexible here in terms of supporting various complex strategies.
I think i'd prefer true/false
API as well.
And not sure.. but cant we do closures somehow to streamline the process?
public function __construct(StoreInterface $decorated, $retrySleep = 50, $retryCount = PHP_INT_MAX) | ||
{ | ||
$this->decorated = $decorated; | ||
$this->retryCount = $retryCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps use $retryCount ?: PHP_INT_MAX
and go 0
in the construct. To keep up with the promise like @iltar mentioned 👍
Regarding the naming of the methods, I've been checking the practices of other languages: Python: lock = Lock()
lock.acquire() # waits if lock is already held
lock.acquire(False) # doesn't wait if lock is already held
lock.release()
if lock.locked() Ruby: lock = Mutex.new
lock.lock # waits if lock is already held
lock.try_lock # doesn't wait if lock is already held
lock.unlock
if lock.locked? PHP pthreads extension $lock = Mutex::create();
Mutex::lock($lock); // waits if lock is already held
Mutex::trylock($lock); // doesn't wait if lock is already held
Mutex::unlock($lock);
// no method to check if the lock is locked Java: Lock lock = ...;
lock.lock(); // waits if lock is already held
lock.tryLock(); // doesn't wait if lock is already held
lock.unlock();
// no method to check if the lock is locked It looks like the most common choice is: |
*/ | ||
public function generateToken() | ||
{ | ||
return base64_encode(random_bytes($this->entropy / 8)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest inlining this and remove RandomTokenGenerator
and TokenGeneratorInterface
: they are just internal details.
*/ | ||
public function createLock($resource) | ||
{ | ||
return new Lock(new Key($resource), $this->store); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about removing the factory and move createLock to the store interface?
); | ||
|
||
// Silence error reporting | ||
set_error_handler(function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
single line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed
return; | ||
} | ||
|
||
$fileName = sprintf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
single line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a limit to the line length in CS?
API choices: IMO lock and unlock fit well when the subject is the resource. ie. I like the split in lock/tryLock, I think I'll apply this change unless someone prefer the $blocking flag? About the return true/false vs \Exception. Personally, I see it like "lock me the resource right now" instead of "can you please lock the resource". If the lock is not acquired, it means sometinhg goes wrong, that's why I excpect an exception. |
src/Symfony/Component/Lock/Key.php
Outdated
$this->resource = (string) $resource; | ||
} | ||
|
||
public function getResource() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__toString()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea
src/Symfony/Component/Lock/Key.php
Outdated
} | ||
|
||
/** | ||
* @return mixed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be removed as it does not say anything useful
src/Symfony/Component/Lock/Lock.php
Outdated
$this->store->expire($this->key, $ttl); | ||
$this->logger->info('Expiration defined for lock "{resource}" for "{ttl}" seconds', array('resource' => $this->key->getResource(), 'ttl' => $ttl)); | ||
} catch (LockConflictedException $e) { | ||
$this->logger->info('Fail to define a expiration for the lock "{resource}". Someone else acquired the lock', array('resource' => $this->key->getResource())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an expiration
, same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use a single sentence here instead of 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't see how to merge this two sentences without removing a part (which are both important IMO) :(
Failed to define an expiration for the "{resource}" lock because someone else acquired it
?
src/Symfony/Component/Lock/Lock.php
Outdated
$this->logger->info('Fail to define a expiration for the lock "{resource}". Someone else acquired the lock', array('resource' => $this->key->getResource())); | ||
throw $e; | ||
} catch (\Exception $e) { | ||
$this->logger->info('Fail to define a expiration for the lock "{resource}"', array('resource' => $this->key->getResource(), 'exception' => $e)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the {resource} lock.
interface LockInterface | ||
{ | ||
/** | ||
* Acquire the lock. If the lock is acquired by someone else, the parameter `blocking` determines whether or not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acquires, same everywhere else.
interface QuorumInterface | ||
{ | ||
/** | ||
* Returns Whether or not the quorum is met. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra cap (same below)
); | ||
|
||
// Silence error reporting | ||
set_error_handler(function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed
|
||
/** | ||
* RetryTillSaveStore is a StoreInterface implementation which decorate a non blocking StoreInterface to provide a | ||
* kind of blocking storage. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"a kind of" should probably be removed.
public function exists(Key $key); | ||
|
||
/** | ||
* Retrieves a lock for the given resource. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It retrieves or it creates?
src/Symfony/Component/Lock/Lock.php
Outdated
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function acquire($blocking = false, $ttl = 300.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about moving the default ttl
value to the constructor instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this parameter was to let the user choose a specific duration for one call.
Given the easiest (and probably the most used) workflow would be to use the Store
to create the Lock
, it does not make sens to move this parameter in the __constructor: If we want a fixed ttl value, we can just remove it and let the store use what he want.
I'll replace the default value 300.0
by a null
. It let the user to override the default store configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally move the parameter in the constructor (as suggested) and remove it from the refresh method too (because this method serves the same purpose).
src/Symfony/Component/Lock/Lock.php
Outdated
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function refresh($ttl = 300.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make the ttl
argument required, without a default value.
|
||
return; | ||
} catch (LockConflictedException $e) { | ||
usleep($this->retrySleep * 1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add randomness to avoid eternal collision
namespace Symfony\Component\Lock\Exception; | ||
|
||
/** | ||
* InvalidArgumentException is thrown when a service had been badly initialized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it has a specific usage, the name should reflect it.
Pushed a new commit to remove the 2 interfaces At first it was a god thing to split it 3 interfaces to separate responsibilities, but I realize that it was a pain to decorate the stores because we both need to implement every interfaces and are not sure that the decorated store will implement every interfaces. In this case, the decorator don't know what to do. |
@jderusse you need to also add the new component in the |
src/Symfony/Component/Lock/LICENSE
Outdated
@@ -0,0 +1,19 @@ | |||
Copyright (c) 2016 Fabien Potencier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be updated to 2016-2017
I suppose
816523b
to
f5b20bd
Compare
src/Symfony/Component/Lock/Lock.php
Outdated
* | ||
* @author Jérémy Derussé <jeremy@derusse.com> | ||
*/ | ||
final class Lock implements LockInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this not implement LoggerAwareInterface
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
*/ | ||
public function createLock($resource, $ttl = 300.0) | ||
{ | ||
return new Lock(new Key($resource), $this, $ttl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
making stores create the lock by passing $this
to the Lock instance makes it very hard to use composition because the Lock instance must always be created by the outside store in this case instead of delegating the call to createLock
(otherwise, the lock has the wrong store, as it ends up calling directly the inner store).
IMO, the Store should not be the lock factory at the same time. Mixing these responsibilities is what makes things hard.
We should either have a separate factory (in which the store is injected, and so decoration works fine as the factory would get the outside store) or let the instantiation be done in userland when using the library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was the first implementation (I like the separation of responsibility too).
But, as suggested by @nicolas-grekas in this comment I move the factory into the StoreInterface and that's why I create the AbstractStore to avoid code duplication (which I don't really like too).
The issue with the factory is was an empty class like without real value:
class StoreFactory
{
private $store;
public function __construct(StoreInterface $store)
{
$this->store = $store;
}
public function createLock($resource, $ttl = 300.0)
{
return new Lock(new Key($resource), $this->lock, $ttl);
}
}
And IMO, the outer store may create the lock instead of delegating the creation to the inner store. The abstractStore do the job (maybe a trait could be a better solution than the abstraction)
What do you think ?
(just to be sure, you are talking about decoration instead of composition ?)
try { | ||
$store->delete($key); | ||
} catch (\Exception $e) { | ||
} catch (\Throwable $e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
catching all exceptions silently would be a debugging nightmare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, but with a notice level given the deletion failure may not be an issue
$store->putOffExpiration($key, $ttl); | ||
++$successCount; | ||
} catch (\Exception $e) { | ||
++$failureCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are loosing all exceptions messages here, making debugging really hard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
||
/** | ||
* Tests blocking locks thanks to pcntl. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing @requires extension pcntl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't aware of this feature. Thanks. I also annotate \Redis tests
$drift = 20000; | ||
|
||
if (PHP_VERSION_ID < 50600 || defined('HHVM_VERSION_ID')) { | ||
$this->markTestSkipped('Php 5.5 does not keep resource in child forks'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message is wrong in the case of HHVM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
|
||
public function getStore() | ||
{ | ||
$redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if there is no such env variable ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part of the test is mostly a copy/paste of the RedisAdapterTest from the Cache component.
The variable is define is the phpunit.xml
file and should exists.
I don't think it worst it to check whether or not someone destroy the config file and expect a successful result.
I think a little explanation about clock drift (which is introduced) could be a plus, a link in code comment, or in the docs to automatically answer about the "why" when people look at the code could be useful:) |
@Prophet777 You're absolutely right, but this PR don't yet introduce a clock drift imlplementation. I'll do it in a next PR or in a other PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My 2 (literally) cents.
*/ | ||
public function putOffExpiration(Key $key, $ttl) | ||
{ | ||
// do nothing, the flock locks forever. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid comment, probably copy-pasted.
} | ||
|
||
// Have to be a \Predis\Client | ||
return call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that constructor allows only some classes and most of them are used above, but wouldn't be more readable to check instance of \Predis\Client
too? After all if
statements exception could be thrown that script could not be evaluated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably I'm late, but in the past I've worked with an almost identical lock model, and it almost never worked. If the process responsible for releasing the lock dies, most of the time the lock needs to be released manually (deleting the lock file or the redis record). The timeout mitigates the problem but does not solve it. |
{ | ||
$this->store->delete($this->key); | ||
|
||
if ($this->store->exists($this->key)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a concurrent environment, another process might have created the same lock here. exists
will return true even if the previous delete
was successful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the lock had been acquired by an other instance of key
, the store will treat them like 2 different keys and will return false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, i got it. there is a "token" in between, that is always random, for the same key
you have different "tokens"
* @throws LockConflictedException If the lock is acquired by someone else in blocking mode | ||
* @throws LockAcquiringException If the lock can not be acquired | ||
*/ | ||
public function acquire($blocking = false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid concurrency issues this should return a unique reference for the lock, so the release should work by using this reference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unique reference is in the key. By passing the key to the methods save/delete/exists the concurrency is already handled
What about adding a MysqlStore using GET_LOCK() / RELEASE_LOCK() / IS_FREE_LOCK() functions ? It's robust and available in all MySQL versions. |
@goetas Then I've got a good news to you, the actual implementation does not suffer for such issue. Thanks to @jocel1 I didn't dig into MySQL store, but it's on my todo list. Do you want to create that PR? |
@jocel1 In mysql there is a fundamental concept that PHP has not, the connection. As the documentation says:
At the end of the connection, the lock will be automatically released. |
@goetas yup that's why it also great to easily handle the dying script case: there's nothing specific to do :) |
*/ | ||
public function __construct(\Memcached $memcached, $initialTtl = 300) | ||
{ | ||
if (!static::isSupported()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to have an object instance of \Memcached
and not having the memcached
extension loaded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's technically possible that someone implement a \Memcached
class.
If the class cover the original extension behaviors and is compatible, everything'll be fine.
If not, that's a weird case and should be addressed IMO.
@jderusse Can you point me what guarantees that |
I have tested the With Flock, if the scripts terminates anomaly, the lock gets released by the kernel, but for redis this does not happen. I guess that memcache suffers from the same problem. $predis = new \Predis\Client('tcp://localhost:6379');
$store = new \Symfony\Component\Lock\Store\RedisStore($predis);
$factory = new \Symfony\Component\Lock\Factory($store);
$lock = $factory->createLock('foo');
if ($lock->acquire()) {
if (!rand(0, 1)) exit; // simulating failure
$lock->release();
} After the failure occurs, the only way to unlock the lock, is to remove manually the entry from redis. |
@goetas None. But I can guarantee that Memcache will delete the key bat it own, when the TTL will expire. @jocel1 it could be an issue to me. What if the connection closes while you think you are in lock phase (network issue, or mysql read timeout, ...) |
i knew this, the standard TTL that many key-value store have |
I suggest to read the (not yet merged) documentation about this https://github.com/symfony/symfony-docs/pull/7364/files |
@jderusse thanks for the explanation. The documentation is clear and highlights the differences between different stores (advantages and disadvantages). At the beginning I was thinking that the all the stores implementations have to fulfill all the requirements (event when hardy possible) |
Mysql and postgres locks can be interesting to implement since they offer both advantages of local and remote locks :) |
return false; | ||
} catch (\Exception $e) { | ||
$this->logger->warning('Failed to lock the "{resource}".', array('resource' => $this->key, 'exception' => $e)); | ||
throw new LockAcquiringException('', 0, $e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jderusse I suggest using a non-empty exception message for better DX
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed..
If the connection closes on the locker side, it could indeed be problematic if you're not making subsequent requests on the db (in this case, the subsequent query would fail as well). But I would expect this to occur far less often than a TTL timeout or even an eviction prior to key expiry on memcached. |
Can we eliminate the memcached store? Memcached is notorious for evicting keys that aren't even to their exptime because they are LRU than something else in the same slab class. By having it, developers will trust it more than they should. It's great as a cache, but really shouldn't be used as a persistent concurrent locking mechanism. Thoughts? |
Hi @patrick-mcdougle, can you please open an issue in order to follow this point? |
This PR was merged into the master branch. Discussion ---------- Add docs for the Lock component See symfony/symfony#21093 Commits ------- 3035fe9 Rename quorum into strategy d4326c0 Reduce usage of "you" d6a218c Rename quorum strategy 17248e1 Remove codeblock 6b6d865 Final review and some minor rewords/simplifications 2498ccd Fix typo d1d3b71 Fixed minor typos and syntax issues 60bfe03 Fix typo 6469016 Add the Lock Component
…ry (corphi) This PR was merged into the 3.4 branch. Discussion ---------- [Lock][PhpDoc] There is no attempt to create the directory | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - When the lock component [was](#21093) [created](#22597), the `FlockStore` implementation was copied from the filesystem component. But the dependency to its origin was dropped, and as part of that, the attempt to create the parent directory for the locks. The PhpDoc wasn’t updated to reflect this change. Commits ------- 93a9bd3 PhpDoc: There is no attempt to create the directory
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
Configured with something like
If you need to lock serveral resource on runtime, wou'll nneed to inject the LockFactory.
Configured with something like
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.
Naming anc implementation choise
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.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
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.
I choose to use a Interface even if there is only one implementaiton to offer an extension point here
TODO
In this PR
In other PR