[Semaphore] Fixed some bugs

This commit is contained in:
Grégoire Pineau 2020-08-28 15:57:12 +02:00
parent 2c4e215ee9
commit 3c943b94ed
6 changed files with 60 additions and 19 deletions

View File

@ -1,6 +1,10 @@
Semaphore Component Semaphore Component
=================== ===================
The Semaphore Component manages
[semaphores](https://en.wikipedia.org/wiki/Semaphore_(programming)), a mechanism
to provide exclusive access to a shared resource.
**This Component is experimental**. **This Component is experimental**.
[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) [Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html)
are not covered by Symfony's are not covered by Symfony's

View File

@ -78,9 +78,17 @@ class RedisStore implements PersistingStoreInterface
$script = file_get_contents(__DIR__.'/Resources/redis_put_off_expiration.lua'); $script = file_get_contents(__DIR__.'/Resources/redis_put_off_expiration.lua');
if ($this->evaluate($script, sprintf('{%s}', $key), [time() + $ttlInSecond, $this->getUniqueToken($key)])) { $ret = $this->evaluate($script, sprintf('{%s}', $key), [time() + $ttlInSecond, $this->getUniqueToken($key)]);
// Occurs when redis has been reset
if (false === $ret) {
throw new SemaphoreExpiredException($key, 'the script returns false'); throw new SemaphoreExpiredException($key, 'the script returns false');
} }
// Occurs when redis has added an item in the set
if (0 < $ret) {
throw new SemaphoreExpiredException($key, 'the script returns a positive number');
}
} }
/** /**

View File

@ -9,10 +9,12 @@ if added == 1 then
end end
-- Extend the TTL -- Extend the TTL
local curentTtl = redis.call("TTL", weightKey) local maxExpiration = redis.call("ZREVRANGE", timeKey, 0, 0, "WITHSCORES")[2]
if curentTtl < now + ttlInSecond then if nil == maxExpiration then
redis.call("EXPIRE", weightKey, curentTtl + 10) return 1
redis.call("EXPIRE", timeKey, curentTtl + 10)
end end
redis.call("EXPIREAT", weightKey, maxExpiration + 10)
redis.call("EXPIREAT", timeKey, maxExpiration + 10)
return added return added

View File

@ -34,10 +34,8 @@ redis.call("ZADD", timeKey, now + ttlInSecond, identifier)
redis.call("ZADD", weightKey, weight, identifier) redis.call("ZADD", weightKey, weight, identifier)
-- Extend the TTL -- Extend the TTL
local curentTtl = redis.call("TTL", weightKey) local maxExpiration = redis.call("ZREVRANGE", timeKey, 0, 0, "WITHSCORES")[2]
if curentTtl < now + ttlInSecond then redis.call("EXPIREAT", weightKey, maxExpiration + 10)
redis.call("EXPIRE", weightKey, curentTtl + 10) redis.call("EXPIREAT", timeKey, maxExpiration + 10)
redis.call("EXPIRE", timeKey, curentTtl + 10)
end
return true return true

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Semaphore\Tests\Store;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException; use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException;
use Symfony\Component\Semaphore\Exception\SemaphoreExpiredException;
use Symfony\Component\Semaphore\Key; use Symfony\Component\Semaphore\Key;
use Symfony\Component\Semaphore\PersistingStoreInterface; use Symfony\Component\Semaphore\PersistingStoreInterface;
@ -28,7 +29,7 @@ abstract class AbstractStoreTest extends TestCase
{ {
$store = $this->getStore(); $store = $this->getStore();
$key = new Key('key', 1); $key = new Key(__METHOD__, 1);
$this->assertFalse($store->exists($key)); $this->assertFalse($store->exists($key));
$store->save($key, 10); $store->save($key, 10);
@ -41,8 +42,8 @@ abstract class AbstractStoreTest extends TestCase
{ {
$store = $this->getStore(); $store = $this->getStore();
$key1 = new Key('key1', 1); $key1 = new Key(__METHOD__.'1', 1);
$key2 = new Key('key2', 1); $key2 = new Key(__METHOD__.'2', 1);
$store->save($key1, 10); $store->save($key1, 10);
$this->assertTrue($store->exists($key1)); $this->assertTrue($store->exists($key1));
@ -65,7 +66,7 @@ abstract class AbstractStoreTest extends TestCase
{ {
$store = $this->getStore(); $store = $this->getStore();
$resource = 'resource'; $resource = __METHOD__;
$key1 = new Key($resource, 1); $key1 = new Key($resource, 1);
$key2 = new Key($resource, 1); $key2 = new Key($resource, 1);
@ -100,7 +101,7 @@ abstract class AbstractStoreTest extends TestCase
{ {
$store = $this->getStore(); $store = $this->getStore();
$resource = 'resource'; $resource = __METHOD__;
$key1 = new Key($resource, 2); $key1 = new Key($resource, 2);
$key2 = new Key($resource, 2); $key2 = new Key($resource, 2);
$key3 = new Key($resource, 2); $key3 = new Key($resource, 2);
@ -144,7 +145,7 @@ abstract class AbstractStoreTest extends TestCase
{ {
$store = $this->getStore(); $store = $this->getStore();
$resource = 'resource'; $resource = __METHOD__;
$key1 = new Key($resource, 4, 2); $key1 = new Key($resource, 4, 2);
$key2 = new Key($resource, 4, 2); $key2 = new Key($resource, 4, 2);
$key3 = new Key($resource, 4, 2); $key3 = new Key($resource, 4, 2);
@ -184,17 +185,40 @@ abstract class AbstractStoreTest extends TestCase
$store->delete($key3); $store->delete($key3);
} }
public function testPutOffExpiration()
{
$store = $this->getStore();
$key = new Key(__METHOD__, 4, 2);
$store->save($key, 20);
$store->putOffExpiration($key, 20);
// just asserts it doesn't throw an exception
$this->addToAssertionCount(1);
}
public function testPutOffExpirationWhenSaveHasNotBeenCalled()
{
// This test simulate the key has expired since it does not exist
$store = $this->getStore();
$key1 = new Key(__METHOD__, 4, 2);
$this->expectException(SemaphoreExpiredException::class);
$this->expectExceptionMessage('The semaphore "Symfony\Component\Semaphore\Tests\Store\AbstractStoreTest::testPutOffExpirationWhenSaveHasNotBeenCalled" has expired: the script returns a positive number.');
$store->putOffExpiration($key1, 20);
}
public function testSaveTwice() public function testSaveTwice()
{ {
$store = $this->getStore(); $store = $this->getStore();
$resource = 'resource'; $key = new Key(__METHOD__, 1);
$key = new Key($resource, 1);
$store->save($key, 10); $store->save($key, 10);
$store->save($key, 10); $store->save($key, 10);
// just asserts it don't throw an exception // just asserts it doesn't throw an exception
$this->addToAssertionCount(1); $this->addToAssertionCount(1);
$store->delete($key); $store->delete($key);

View File

@ -18,6 +18,11 @@ namespace Symfony\Component\Semaphore\Tests\Store;
*/ */
class RedisStoreTest extends AbstractRedisStoreTest class RedisStoreTest extends AbstractRedisStoreTest
{ {
protected function setUp(): void
{
$this->getRedisConnection()->flushDB();
}
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
try { try {