Skip to content

[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

Closed
wants to merge 3 commits into from

Conversation

jderusse
Copy link
Member

@jderusse jderusse commented Mar 23, 2017

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.

@jderusse jderusse changed the title Automaticaly release lock on destruction [Lock] Automaticaly release lock when user forget it Mar 23, 2017
@jderusse jderusse force-pushed the lock-auto-release branch 4 times, most recently from c4f4b4c to 309a6c1 Compare March 23, 2017 21:55
@stof
Copy link
Member

stof commented Mar 24, 2017

this would force to have extra accesses to the store for everyone writing correct code releasing locks.
and it does not ensure it will be released: #22115 (comment)

So I'm rather -1 on this.

the second commit adding exception messages is good though (please submit it separately)

@nicolas-grekas nicolas-grekas added this to the 3.3 milestone Mar 24, 2017
@nicolas-grekas
Copy link
Member

@stof should the store release the lock then?
IMHO, it'd be really nice to release any remaining lock once not used anymore.
Not as a feature one could rely on, but as a "be-clean" strategy.

@stof
Copy link
Member

stof commented Mar 30, 2017

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)

@jderusse
Copy link
Member Author

I add a isDirty parameter to address the suggestion of @stof .
I'm not a big fan of the name of this variable, if you've a better idea, I'm listening (I don't wont to use a isLocked variable to avoid confusion, with the only source of trust: le Store)

@jderusse jderusse force-pushed the lock-auto-release branch 2 times, most recently from 2e90d1e to 5144cc1 Compare March 31, 2017 19:11
@nicolas-grekas
Copy link
Member

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.

@jderusse
Copy link
Member Author

jderusse commented Apr 3, 2017

I wonder if we should take this use-case into account @nicolas-grekas
Most of stores won't be compatible (stores linked to the process like flock, semaphore, etc..)

@jderusse jderusse force-pushed the lock-auto-release branch from 5144cc1 to c3bda26 Compare April 3, 2017 11:13
@fabpot
Copy link
Member

fabpot commented Apr 26, 2017

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

@nicolas-grekas nicolas-grekas modified the milestones: 3.3, 3.4 Apr 29, 2017
@jderusse jderusse changed the base branch from master to 3.4 June 19, 2017 21:24
@nicolas-grekas
Copy link
Member

I'm personnaly in favor of having auto-unlock by default, and manual unlock as opt-in. Less space for mistakes.

@jderusse
Copy link
Member Author

What do you think about

class LockFactory
{
  public function createLock(): AutoRealesedLock
  public function createManualLock(): Lock
}

Can we change the interface contract or is it considered as BC break?

@nicolas-grekas
Copy link
Member

Do we need a new Lock type of can we deal with a new constructor/factory arg ($autoUnlock?)
About the interface, we can change everything, this has never been tagged so we are not bound by any BC promise.

@robfrawley
Copy link
Contributor

I personally think it is important to be able to determine the type of lock, whether that is allowed by having separate AutoRealesedLock and Lock

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.

@jderusse jderusse force-pushed the lock-auto-release branch from c3bda26 to 7e89144 Compare July 11, 2017 21:23
@nicolas-grekas
Copy link
Member

do we really have a use case were we would have conditional branching base on this?

@jderusse jderusse force-pushed the lock-auto-release branch from 7e89144 to c6bef05 Compare July 11, 2017 21:38
* @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
Copy link
Member

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

Copy link
Member Author

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

* @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here


$this->logger = new NullLogger();
}

/**
* Automatically release the underlying lock when the object is destructed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

releases

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -32,22 +32,44 @@
private $store;
private $key;
private $ttl;
private $autoRelease;
private $dirty = false;

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.

Copy link
Member Author

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))

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.

Copy link
Member Author

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:

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

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 😄

return;
}

try {
Copy link
Member

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?

Copy link
Member Author

@jderusse jderusse Sep 26, 2017

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't "release" idempotent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed. Fixed

@jderusse jderusse force-pushed the lock-auto-release branch 2 times, most recently from ddee0b9 to 0897b05 Compare September 26, 2017 21:18
@jderusse jderusse force-pushed the lock-auto-release branch 2 times, most recently from fbcfdb2 to 12110bd Compare September 26, 2017 21:40
* @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
Copy link
Member

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

* @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when... (see previous comment)

*/
public function __destruct()
{
if (!$this->autoRelease) {
Copy link
Member

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)) {
Copy link
Member

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?

Copy link
Member Author

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.

@fabpot
Copy link
Member

fabpot commented Oct 1, 2017

What's the status of this PR? Mergeable? That's probably a PR we need to close or merge before feature freeze.

@jderusse
Copy link
Member Author

jderusse commented Oct 1, 2017

All comments are take into account. IMO ready to merge.ping @symfony/deciders

@fabpot
Copy link
Member

fabpot commented Oct 1, 2017

Thank you @jderusse.

@fabpot fabpot closed this Oct 1, 2017
fabpot added a commit that referenced this pull request Oct 1, 2017
…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
This was referenced Oct 18, 2017
@jderusse jderusse deleted the lock-auto-release branch August 2, 2019 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants