-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Lock] Automaticaly release lock when user forget it #22132
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
c4f4b4c
to
309a6c1
Compare
this would force to have extra accesses to the store for everyone writing correct code releasing locks. So I'm rather -1 on this. the second commit adding exception messages is good though (please submit it separately) |
@stof should the store release the lock then? |
What we could do to avoid performance overhead on the store access is to add a private property storing whether we already asked to release the lock, to skip the logic in the destructor in this case (and reset this private property if we ask to acquire the lock again) |
I add a |
2e90d1e
to
5144cc1
Compare
Note that on a command tunnel, it may be nice to allow one to "lock" some item in their "stock" accros several pages, so one should be able to opt-out from this auto-unlocking. |
I wonder if we should take this use-case into account @nicolas-grekas |
5144cc1
to
c3bda26
Compare
We need to take a decision on this one. Either merge or close, but before 3.3. As first beta is probably coming out next week, what do you think? @symfony/deciders |
I'm personnaly in favor of having auto-unlock by default, and manual unlock as opt-in. Less space for mistakes. |
What do you think about
Can we change the interface contract or is it considered as BC break? |
Do we need a new Lock type of can we deal with a new constructor/factory arg ($autoUnlock?) |
I personally think it is important to be able to determine the type of lock, whether that is allowed by having separate if (!$lock instanceof AutoRealesedLock) {
// do stuff
} or through a method on the lock instance itself if (!$lock->isAutoReleased()) {
// do stuff
} I don't have any particular preference. |
c3bda26
to
7e89144
Compare
do we really have a use case were we would have conditional branching base on this? |
7e89144
to
c6bef05
Compare
* @param float $ttl maximum expected lock duration | ||
* @param string $resource The resource to lock | ||
* @param float $ttl Maximum expected lock duration in seconds | ||
* @param bool $autoRelease Whether or not the lock will automatically be released |
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.
Whether to automatically release the lock 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.
fixed.
Same in Lock.php
src/Symfony/Component/Lock/Lock.php
Outdated
* @param Key $key Resource to lock | ||
* @param StoreInterface $store Store used to handle lock persistence | ||
* @param float|null $ttl Maximum expected lock duration in seconds | ||
* @param bool $autoRelease Whether or not the lock will automatically be released |
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.
same here
src/Symfony/Component/Lock/Lock.php
Outdated
|
||
$this->logger = new NullLogger(); | ||
} | ||
|
||
/** | ||
* Automatically release the underlying lock when the object is destructed. |
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.
releases
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
c6bef05
to
4367e08
Compare
4367e08
to
cca1805
Compare
@@ -32,22 +32,44 @@ | |||
private $store; | |||
private $key; | |||
private $ttl; | |||
private $autoRelease; | |||
private $dirty = 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.
@jderusse I think what you are tracking here is simply the ownership status of the current Lock object, which IMO should be renamed to LockHandler or something else less ambiguous with the lock which is the shared resource between many lock handlers.
So it would be great to rename "dirty" property to "owner" and add ownership concept to LockHandler API.
It will allows to:
- ensure that an owning lock handler can only release a lock (otherwise throws LogicException).
- ensure that a non-owning lock handler can only acquire a lock (otherwise throws LogicException).
- expose to the user an isOwner() method (!= isAcquired())
By the way this will also help to fix an issue I've found in CombinedStore implementation where you potentially release (as a cleaning step) acquired but not owned locks.
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.
By design, the state of the lock is stored in the Key
object. The Lock
object is only a wraper and helper between the store and the key.
The following code works
$key = new Key(uniqid(__METHOD__, true));
$lock1 = new Lock($key, $store);
$lock2 = new Lock($key, $store);
$lock1->acquire();
$lock2->isAcquired(); // true
The purpose of this variable is not to track the ownership of the Lock, but to quickly know if the lock is acquired and avoid to call havy operation on the Lock destruction when the lock has not been acquired or has already been release (see comment #22132 (comment))
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 point is just that ownership must be tracked to avoid ownership issues such as (for non reentrant lock, which is the case here):
- acquiring an already owned lock (i.e. already acquired by the current lock object).
- releasing a non owned lock (even and especially if owned by someone else).
This kind of issue can arise as long as program flaws exist, and when it happens throwing something like a LogicException is far more better than corrupt the system IMO.
And I think there is an ownership issue right here.
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.
Unicity (or ownership) is tracked and stored in the Key
by the store (A Key is not a resource)
You can't release a Lock with a different key.
The ownership is handled:
- by storing the filepointer in flock https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Lock/Store/FlockStore.php#L123
- by storing/and comparing a random value in Memcached and Redis https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Lock/Store/RedisStore.php#L105
Stores won't throw exception if you try to delete a key which is not the owner, but they neither don't deleted the lock.
$key1 = new Key('foo');
$key2 = new Key('foo');
$lock1 = new Lock($store, key1);
$lock2 = new Lock($store, key2);
$lock1->acquire(); // return true
$lock2->acquire(); // return false
$lock2->release(); // do nothing
$lock2->isAcquire(); // return false
$lock1->isAcquire(); // return true
$lock2->acquire(); // 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.
Ok nice, but I'm just surprised to have ownership implemented on lock provider side (stores) since it could be simply and in a single place tracked in Lock class with a bool flag.
So the behavior here is indeed correct since non owned acquired locks will be silently discarded.
Correct but just still hard to understand IMO (I'd prefer explicitly checking isOwner() before calling release()).
And I still find very useful to have LogicException thrown when I make something illogic 😄
src/Symfony/Component/Lock/Lock.php
Outdated
return; | ||
} | ||
|
||
try { |
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 the try catch actually required?
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 not throw an exception when the auto-release try to release the lock...
- leave the try/cacth and you may miss exception when you count on the auto-release to release the lock
- remove the try/catch and you may have an unexpected exception when you manually release the lock and the auto-release retry to do it
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.
isn't "release" idempotent?
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. Fixed
ddee0b9
to
0897b05
Compare
fbcfdb2
to
12110bd
Compare
* @param float $ttl maximum expected lock duration | ||
* @param string $resource The resource to lock | ||
* @param float $ttl Maximum expected lock duration in seconds | ||
* @param bool $autoRelease Whether to automatically release the lock 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.
Whether to automatically release the lock or not when the lock instance is destroyed
src/Symfony/Component/Lock/Lock.php
Outdated
* @param Key $key Resource to lock | ||
* @param StoreInterface $store Store used to handle lock persistence | ||
* @param float|null $ttl Maximum expected lock duration in seconds | ||
* @param bool $autoRelease Whether to automatically release the lock 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.
when... (see previous comment)
src/Symfony/Component/Lock/Lock.php
Outdated
*/ | ||
public function __destruct() | ||
{ | ||
if (!$this->autoRelease) { |
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.
one "if" is enough in the method:
if ($this->autoRelease && $this->dirty && $this->isAcquired()) { //...
@@ -125,6 +147,7 @@ public function isAcquired() | |||
public function release() | |||
{ | |||
$this->store->delete($this->key); | |||
$this->dirty = false; | |||
|
|||
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.
unrelated, but just wondering: isn't this creating a race condition?
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.
No, unless you share the key with an other process (i'm talking about the key, not the resource).
The method exists
check both the "resource" and the "owner". You may have a race condition by using pcntl_fork (or serializeing then share the key) and both processes try to release and acquire the lock on the same time. But IMO, that's a weird case.
BTW, we don't provide methods to check if the lock is acquired by someone else.
12110bd
to
70a01ac
Compare
What's the status of this PR? Mergeable? That's probably a PR we need to close or merge before feature freeze. |
All comments are take into account. IMO ready to merge.ping @symfony/deciders |
Thank you @jderusse. |
…jderusse) This PR was squashed before being merged into the 3.4 branch (closes #22132). Discussion ---------- [Lock] Automaticaly release lock when user forget it | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | The objective of this PR is to automatically release the Lock when the user forget to call the `release` method (like the `fclose` function does in, php core). The implementation is comparable to #22115 but the purpose is not the same. I create a new PR to not mix things. Commits ------- 2eedc1f [Lock] Automaticaly release lock when user forget it
The objective of this PR is to automatically release the Lock when the user forget to call the
release
method (like thefclose
function does in, php core).The implementation is comparable to #22115 but the purpose is not the same.
I create a new PR to not mix things.