Skip to content

[Lock] Refine the contract and implementation for Stores to handle Not Expirable Store cases. #28727

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Lock\Exception;

/**
* NotExpirableStoreException is thrown when a store doesn't support expiration of locks.
*
* @author Ganesh Chandrasekaran <gchandrasekaran@wayfair.com>
*/
class NotExpirableStoreException extends \LogicException implements ExceptionInterface
{
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/Lock/Lock.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockExpiredException;
use Symfony\Component\Lock\Exception\LockReleasingException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;

/**
* Lock is the default implementation of the LockInterface.
Expand Down Expand Up @@ -124,6 +125,8 @@ public function refresh($ttl = null)
}

$this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $ttl));
} catch (NotExpirableStoreException $e) {
$this->logger->notice('The store does not support expiration of locks.', array('store' => $this->store));
Copy link
Member

Choose a reason for hiding this comment

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

The method refresh is called internally when calling acquire with a non-null TTL (which is the default case when using the Factory with default arguments)

When using a NotExpirableStore (like flock) the logger will be full of notice.
Same comment when combining a non-expirable store and expirable one (flock + redis for instance)

IMHO You should either silent this exception when calling acquire or update the factory to set a null TTL when the store does not support expiration.

Would be great from a user point of view to known whether or not the store supports expiration.

interface StoreInterface {
  public function save(Key $key);
  public function waitAndSave(Key $key);
  public function delete(Key $key);
  public function exists(Key $key);
}

interface ExpirableStoreInterface extends StoreInterface {
  public function supportsExpiration(): bool
  public function putOffExpiration(Key $key, $ttl);
}

} catch (LockConflictedException $e) {
$this->dirty = false;
$this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key));
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/Lock/Store/CombinedStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Component\Lock\Exception\InvalidArgumentException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockExpiredException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;
use Symfony\Component\Lock\Exception\NotSupportedException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\StoreInterface;
Expand Down Expand Up @@ -115,6 +116,9 @@ public function putOffExpiration(Key $key, $ttl)

$store->putOffExpiration($key, $adjustedTtl);
++$successCount;
} catch (NotExpirableStoreException $e) {
$this->logger->notice('The store does not support expiration of locks.', array('store' => $store));
++$successCount;
} catch (\Exception $e) {
$this->logger->warning('One store failed to put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
++$failureCount;
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Lock/Store/FlockStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Lock\Exception\InvalidArgumentException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockStorageException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\StoreInterface;

Expand Down Expand Up @@ -108,7 +109,7 @@ private function lock(Key $key, $blocking)
*/
public function putOffExpiration(Key $key, $ttl)
{
// do nothing, the flock locks forever.
throw new NotExpirableStoreException();
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it's should be treated like a BC break. ping @nicolas-grekas

Copy link
Member

Choose a reason for hiding this comment

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

Looks like a BC break to me, but might be worth doing anyway instead of silently ignoring the call.

}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Lock/Store/SemaphoreStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Lock\Exception\InvalidArgumentException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\StoreInterface;

Expand Down Expand Up @@ -102,7 +103,7 @@ public function delete(Key $key)
*/
public function putOffExpiration(Key $key, $ttl)
{
// do nothing, the semaphore locks forever.
throw new NotExpirableStoreException();
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Lock/Store/ZookeeperStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Lock\Exception\LockAcquiringException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockReleasingException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;
use Symfony\Component\Lock\Exception\NotSupportedException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\StoreInterface;
Expand Down Expand Up @@ -91,7 +92,7 @@ public function waitAndSave(Key $key)
*/
public function putOffExpiration(Key $key, $ttl)
{
throw new NotSupportedException();
throw new NotExpirableStoreException();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Lock/StoreInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Lock\Exception\LockAcquiringException;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Exception\LockReleasingException;
use Symfony\Component\Lock\Exception\NotExpirableStoreException;
use Symfony\Component\Lock\Exception\NotSupportedException;

/**
Expand Down Expand Up @@ -49,6 +50,7 @@ public function waitAndSave(Key $key);
* @param float $ttl amount of second to keep the lock in the store
*
* @throws LockConflictedException
* @throws NotExpirableStoreException
* @throws NotSupportedException
*/
public function putOffExpiration(Key $key, $ttl);
Expand Down
6 changes: 5 additions & 1 deletion src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\Store\CombinedStore;
use Symfony\Component\Lock\Store\RedisStore;
use Symfony\Component\Lock\Store\ZookeeperStore;
use Symfony\Component\Lock\StoreInterface;
use Symfony\Component\Lock\Strategy\StrategyInterface;
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
Expand Down Expand Up @@ -46,7 +47,10 @@ public function getStore()
self::markTestSkipped($e->getMessage());
}

return new CombinedStore(array(new RedisStore($redis)), new UnanimousStrategy());
$zookeeper_server = getenv('ZOOKEEPER_HOST').':2181';
Copy link
Member

Choose a reason for hiding this comment

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

Small typo, should be $zookeeperServer

$zookeeper = new \Zookeeper(implode(',', array($zookeeper_server)));

return new CombinedStore(array(new RedisStore($redis), new ZookeeperStore($zookeeper)), new UnanimousStrategy());
}

/** @var \PHPUnit_Framework_MockObject_MockObject */
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
class FlockStoreTest extends AbstractStoreTest
{
use BlockingStoreTestTrait;
use NotExpiringStoreTestTrait;

/**
* {@inheritdoc}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Lock\Tests\Store;

use Symfony\Component\Lock\Key;

/**
* @author Ganesh Chandrasekaran <gchandrasekaran@wayfair.com>
*/
trait NotExpiringStoreTestTrait
{
/**
* @see AbstractStoreTest::getStore()
*/
abstract protected function getStore();

/**
* @expectedException \Symfony\Component\Lock\Exception\NotExpirableStoreException
*/
public function testPutOffExpirationThrowsException()
{
$store = $this->getStore();
$key = new Key(uniqid(__METHOD__, true));

$store->save($key);
$store->putOffExpiration($key, 10.0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
class SemaphoreStoreTest extends AbstractStoreTest
{
use BlockingStoreTestTrait;
use NotExpiringStoreTestTrait;

/**
* {@inheritdoc}
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
*/
class ZookeeperStoreTest extends AbstractStoreTest
{
use NotExpiringStoreTestTrait;

public function getStore(): ZookeeperStore
{
$zookeeper_server = getenv('ZOOKEEPER_HOST').':2181';
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy