Skip to content
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

[Lock] Check TTL expiration in lock acquisition #22542

Closed
wants to merge 2 commits into from

Conversation

jderusse
Copy link
Member

@jderusse jderusse commented Apr 26, 2017

Q A
Branch? 3.4
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #22452
License MIT
Doc PR NA

This PR improve lock acquisition by throwing exception when the TTL is expired during the lock process.

@jderusse jderusse changed the title Check TTL expiration in lock acquisition [Lock] Check TTL expiration in lock acquisition Apr 26, 2017
@nicolas-grekas nicolas-grekas added this to the 3.3 milestone Apr 27, 2017
@nicolas-grekas
Copy link
Member

Can this wait 3.4? We'd like to close 3.3 asap :)

@jderusse
Copy link
Member Author

From the 3 pending PR related to the components Lock, this one is the most important.

It avoid to acquire an expired Lock (due to network latency for instance). Cf discussion in the issue.

That's said, with an adequate warning in the documentation, this PR is not an absolute requirement.

@nicolas-grekas nicolas-grekas modified the milestones: 3.3, 3.4 Apr 29, 2017
@nicolas-grekas nicolas-grekas changed the base branch from master to 3.4 May 23, 2017 16:48
@jderusse
Copy link
Member Author

PR rebased and fixed add messages in Exceptions.


public function testAbortAfterExpiration()
{
$this->markTestIncomplete('Memcached expecte a TTL greater than 1 sec. Simulating slow network is too complex');
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be marked as skipped not incomplete.

@fabpot
Copy link
Member

fabpot commented Aug 23, 2017

What's the status of this PR? I'd like to merge/close all PRs about the lock component ASAP to avoid having to revert the component for 3.4.

@jderusse
Copy link
Member Author

@fabpot waiting for approvals. As saids in #22542 (comment) this PR is the most important IMO related to lock component.


foreach ($this->stores as $store) {
try {
$store->putOffExpiration($key, $ttl);
if (0.0 >= $adjustedTtl = $expireAt - microtime(true)) {
$this->logger->warning('TTL expires during the put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'ttl' => $ttl));
Copy link
Member

Choose a reason for hiding this comment

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

Message is not clear to me. Can you explain what you are trying to say?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are trying to put off the expiration of the lock (to extend it life duration) but the method take more time to perform that task than the asked TTL.

For instance, when calling putOffExpiration($key, 31) takes 31 seconds.

@@ -117,6 +124,10 @@ public function putOffExpiration(Key $key, $ttl)
}
}

if (microtime(true) >= $expireAt) {
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
Copy link
Member

Choose a reason for hiding this comment

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

If put off is the right verb, then there is a typo here.

Copy link
Member

Choose a reason for hiding this comment

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

missing of after expiration also

return;
$expireAt = microtime(true) + $this->initialTtl;
if (!$this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
// the lock is already acquire. It could be us. Let's try to put off.
Copy link
Member

Choose a reason for hiding this comment

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

acquired.

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

// the lock is already acquire. It could be us. Let's try to put off.
$this->putOffExpiration($key, $this->initialTtl);
if (microtime(true) >= $expireAt) {
throw new LockExpiredException(sprintf('Failed to store the "%s" lock in less than "%f".', $key, $this->initialTtl));
Copy link
Member

Choose a reason for hiding this comment

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

I would remove in less than. I would also add the unit.

Copy link
Member Author

Choose a reason for hiding this comment

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

removed (same for others usage)

@@ -105,6 +111,10 @@ public function putOffExpiration(Key $key, $ttl)
if (!$this->memcached->cas($cas, (string) $key, $token, $ttl)) {
throw new LockConflictedException();
}

if (microtime(true) >= $expireAt) {
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
Copy link
Member

Choose a reason for hiding this comment

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

put off?

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

throw new LockConflictedException();
}

if (microtime(true) >= $expireAt) {
throw new LockExpiredException(sprintf('Failed to put of the expiration the "%s" lock in less than "%f".', $key, $ttl));
Copy link
Member

Choose a reason for hiding this comment

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

same


public function testAbortAfterExpiration()
{
$this->markTestSkipped('Memcached expecte a TTL greater than 1 sec. Simulating slow network is too complex');
Copy link
Member

Choose a reason for hiding this comment

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

typo expects
Simulating a slow network is too hard

@jderusse
Copy link
Member Author

jderusse commented Aug 31, 2017

PR rebased with few changes

  • LockExpiredException does not extends LockConflictedException anymore because they are not related
  • Add a isExpired check in the lock::acquire and lock::refresh for more strength

Copy link
Member

@chalasr chalasr left a comment

Choose a reason for hiding this comment

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

with minor comment


public function testAbortAfterExpiration()
{
$this->markTestSkipped('Memcached expecte a TTL greater than 1 sec. Simulating a slow network is too hard');
Copy link
Member

Choose a reason for hiding this comment

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

typo expects

@jderusse jderusse force-pushed the lock-ttl-check branch 2 times, most recently from a7bc204 to 2cd518e Compare August 31, 2017 12:46
@jderusse jderusse force-pushed the lock-ttl-check branch 4 times, most recently from 81162a6 to 2e4f434 Compare August 31, 2017 13:56
@jderusse
Copy link
Member Author

failling test not related to this PR


if (null === $this->expiringDate || $newExpiringDate < $this->expiringDate) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Switch to float comparison instead of datetime, due to bug in PHP => https://3v4l.org/bSt3d

if (null === $this->expiringDate || $newExpiringDate < $this->expiringDate) {
$this->expiringDate = $newExpiringDate;
if (null === $this->expiringTime || $this->expiringTime > $newTime) {
$this->expiringTime = $newTime;
}
}

public function resetExpiringDate()
Copy link
Member

Choose a reason for hiding this comment

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

Should be resetExpiringTime() now, right?

}
}

public function resetExpiringDate()
{
$this->expiringDate = null;
$this->expiringTime = null;
}

/**
* @return \DateTimeImmutable
*/
public function getExpiringDate()
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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Using microtime is a internal implementation to fix issue with \Datetime comparison. IMHO there is no reason to change the public interface of this class, returning a \Datetime here is more usefull than returning a microtime.

If I have to change it, I would rather stop using ExpiringThing and replace them by lifetime with resetLifetime(), reduceLifetime(float $ttl), isExpired(): bool and getTimeToLive(): float WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

We now have a public method that takes a float and the rest of the API dealing with DateTime, that feels slightly inconsistent to me as well. I'm fine with float only.
Would getTimeToLive() return the remaining ttl at the moment? Or just the original (full) ttl?
Do we really need it? (just wondering)

Copy link
Member Author

Choose a reason for hiding this comment

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

getTimeToLive should returns the raimaining TTL. Returning the initial TTL without knowing when this TTL had been sets is useless.

The purpose is to let the locker knowing if he has time to perform his job.
For instance you iterate over thousands of items. You know that it takes between 1 and 3 seconds to treat 1 item. You can use the the getTimeToLive after each item and when the TTL is below 5 seconds you call the refresh() method to extends the life of your lock.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me

{
$this->expiringDate = null;
return null === $this->expiringTime ? null : $this->expiringTime - microtime(true);
Copy link
Member Author

Choose a reason for hiding this comment

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

Should it be null or (float) PHP_MAX_INT?

Copy link
Member

Choose a reason for hiding this comment

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

I'd keep null with a comment explaining its meaning in the return annotation

Copy link
Member

Choose a reason for hiding this comment

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

👍

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

@chalasr
Copy link
Member

chalasr commented Sep 3, 2017

👍

@fabpot
Copy link
Member

fabpot commented Sep 3, 2017

Thank you @jderusse.

@fabpot fabpot closed this Sep 3, 2017
fabpot added a commit that referenced this pull request Sep 3, 2017
…sse)

This PR was squashed before being merged into the 3.4 branch (closes #22542).

Discussion
----------

[Lock] Check TTL expiration in lock acquisition

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #22452
| License       | MIT
| Doc PR        | NA

This PR improve lock acquisition by throwing exception when the TTL is expired during the lock process.

Commits
-------

f74ef35 [Lock] Check TTL expiration in lock acquisition
This was referenced Oct 18, 2017
@jderusse jderusse deleted the lock-ttl-check branch August 2, 2019 12:17
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.

6 participants