From 239c086735ea2706145491593a57da71b78068fa Mon Sep 17 00:00:00 2001 From: Peter Scopes Date: Thu, 28 Jul 2016 11:18:26 +0100 Subject: [PATCH 1/4] Added composer.lock file and .idea directory (PHPStorm) to the ignore list --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dc8bf6e..742ddf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -vendor/* +.idea/ +vendor/ */logs/* */cache/* composer.phar +composer.lock From 351a2df00c0515d1d9861c49f193dcd8dd40cea7 Mon Sep 17 00:00:00 2001 From: Peter Scopes Date: Thu, 28 Jul 2016 11:18:58 +0100 Subject: [PATCH 2/4] Added namespace to class file and PHPDoc --- src/RedLock.php | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/RedLock.php b/src/RedLock.php index 5f6770a..002f00d 100644 --- a/src/RedLock.php +++ b/src/RedLock.php @@ -1,16 +1,55 @@ + */ class RedLock { + /** + * @var int Seconds to delay retrying + */ private $retryDelay; + + /** + * @var int Number lock attempt retries before giving up + */ private $retryCount; + + /** + * @var float Account for Redis expires precision + */ private $clockDriftFactor = 0.01; + /** + * @var mixed + */ private $quorum; + /** + * @var array[] Array of server information arrays: [host, port, timeout] + * @see \Redis::connect() + */ private $servers = array(); + + /** + * @var array|\Redis[] + */ private $instances = array(); + /** + * RedLock constructor. + * + * @param array[] $servers Each element should an array of host, port, timeout + * @param int $retryDelay Seconds delay between retries + * @param int $retryCount Number of times to retry + * + * @see \Redis::connect() + */ function __construct(array $servers, $retryDelay = 200, $retryCount = 3) { $this->servers = $servers; @@ -21,6 +60,12 @@ function __construct(array $servers, $retryDelay = 200, $retryCount = 3) $this->quorum = min(count($servers), (count($servers) / 2 + 1)); } + /** + * @param string $resource Unique identifier + * @param int $ttl Time to live + * + * @return array|bool + */ public function lock($resource, $ttl) { $this->initInstances(); @@ -70,6 +115,10 @@ public function lock($resource, $ttl) return false; } + /** + * @param array $lock Array returned by RedLock::lock + * @see lock() + */ public function unlock(array $lock) { $this->initInstances(); @@ -81,6 +130,9 @@ public function unlock(array $lock) } } + /** + * Initialise the Redis servers provided in the the constructor. + */ private function initInstances() { if (empty($this->instances)) { @@ -94,11 +146,26 @@ private function initInstances() } } + /** + * @param \Redis $instance Redis instance to attempt to lock + * @param string $resource Unique identifier + * @param string $token Unique token + * @param int $ttl Time to live + * + * @return mixed + */ private function lockInstance($instance, $resource, $token, $ttl) { return $instance->set($resource, $token, ['NX', 'PX' => $ttl]); } + /** + * @param \Redis $instance Redis instance to unlock + * @param string $resource Unique identifier + * @param string $token Unique token + * + * @return mixed + */ private function unlockInstance($instance, $resource, $token) { $script = ' From a51a6984d3abf8b36b90f95b3c6bd02b8226c0b6 Mon Sep 17 00:00:00 2001 From: Peter Scopes Date: Thu, 28 Jul 2016 12:46:41 +0100 Subject: [PATCH 3/4] Added PHPUnit tests --- composer.json | 44 ++++++++------ phpunit.xml | 22 +++++++ src/RedLock.php | 24 ++++---- tests/RedLockTest.php | 110 ++++++++++++++++++++++++++++++++++ tests/RedLockTestListener.php | 61 +++++++++++++++++++ tests/test1.php | 21 ------- 6 files changed, 232 insertions(+), 50 deletions(-) create mode 100644 phpunit.xml create mode 100644 tests/RedLockTest.php create mode 100644 tests/RedLockTestListener.php delete mode 100644 tests/test1.php diff --git a/composer.json b/composer.json index a591c12..03b45ec 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,30 @@ { - "name": "ronnylt/redlock-php", - "description": "Redis distributed locks in PHP", - "authors": [ - { - "name": "Ronny Lopez", - "email": "ronny@tangotree.io" - } - ], - "require": { - "php": "~5.4", - "ext-redis": "~2.2.5" + "name": "ronnylt/redlock-php", + "description": "Redis distributed locks in PHP", + "authors": [ + { + "name": "Ronny Lopez", + "email": "ronny@tangotree.io" }, - - "autoload": { - "psr-4": { - "RedLock\\": "src/" - } + { + "name": "Peter Scopes", + "email": "peter.scopes@gmail.com" } - - + ], + "require": { + "php": "~5.4", + "ext-redis": "~2.2.5" + }, + "require-dev": { + "php": "~5.6", + "phpunit/phpunit": "5.4.*" + }, + "autoload": { + "psr-4": { + "RedLock\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { "": "tests/" } + } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..9b0f5ed --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + ./tests/ + + + + + + \ No newline at end of file diff --git a/src/RedLock.php b/src/RedLock.php index 002f00d..9672e41 100644 --- a/src/RedLock.php +++ b/src/RedLock.php @@ -31,7 +31,7 @@ class RedLock private $quorum; /** - * @var array[] Array of server information arrays: [host, port, timeout] + * @var array[]|\Redis[] Array of server information arrays: [host, port, timeout] * @see \Redis::connect() */ private $servers = array(); @@ -44,13 +44,13 @@ class RedLock /** * RedLock constructor. * - * @param array[] $servers Each element should an array of host, port, timeout - * @param int $retryDelay Seconds delay between retries - * @param int $retryCount Number of times to retry + * @param array[]|\Redis[] $servers Each element should an array of host, port, timeout + * @param int $retryDelay Seconds delay between retries + * @param int $retryCount Number of times to retry * * @see \Redis::connect() */ - function __construct(array $servers, $retryDelay = 200, $retryCount = 3) + public function __construct(array $servers, $retryDelay = 200, $retryCount = 3) { $this->servers = $servers; @@ -62,7 +62,7 @@ function __construct(array $servers, $retryDelay = 200, $retryCount = 3) /** * @param string $resource Unique identifier - * @param int $ttl Time to live + * @param int $ttl Time to live (milliseconds) * * @return array|bool */ @@ -137,11 +137,13 @@ private function initInstances() { if (empty($this->instances)) { foreach ($this->servers as $server) { - list($host, $port, $timeout) = $server; - $redis = new \Redis(); - $redis->connect($host, $port, $timeout); + if (!$server instanceof \Redis) { + list($host, $port, $timeout) = $server; + $server = new \Redis(); + $server->connect($host, $port, $timeout); + } - $this->instances[] = $redis; + $this->instances[] = $server; } } } @@ -150,7 +152,7 @@ private function initInstances() * @param \Redis $instance Redis instance to attempt to lock * @param string $resource Unique identifier * @param string $token Unique token - * @param int $ttl Time to live + * @param int $ttl Time to live (milliseconds) * * @return mixed */ diff --git a/tests/RedLockTest.php b/tests/RedLockTest.php new file mode 100644 index 0000000..966e214 --- /dev/null +++ b/tests/RedLockTest.php @@ -0,0 +1,110 @@ +servers = [ + ['127.0.0.1', self::SERVER_PORT_A, 0.1], + ['127.0.0.1', self::SERVER_PORT_B, 0.1], + ['127.0.0.1', self::SERVER_PORT_C, 0.1], + ]; + } + + public function testLockOk() + { + $redLock = new \RedLock\RedLock($this->servers); + $resource = 'redLock.key'; + $gate = $redLock->lock($resource, 500); + $redLock->unlock($gate); + + // Asserts + $this->assertInternalType('array', $gate); + $this->assertArrayHasKey('validity', $gate); + $this->assertArrayHasKey('token', $gate); + $this->assertArrayHasKey('resource', $gate); + $this->assertEquals($resource, $gate['resource']); + + } + + public function testBlockOk() + { + $redLock = new \RedLock\RedLock($this->servers); + $resource = 'redLock.key'; + + $gateA = $redLock->lock($resource, 500); + $gateB = $redLock->lock($resource, 500); + $redLock->unlock($gateA); + + // Asserts + $this->assertFalse($gateB); + } + + public function testTimeoutOk() + { + $redLock = new \RedLock\RedLock($this->servers); + $resource = 'redLock.key'; + + $gateA = $redLock->lock($resource, 500); + $gateB = $redLock->lock($resource, 500); + + $this->assertInternalType('array', $gateA); + $this->assertFalse($gateB); + + usleep(500000); + + $gateB = $redLock->lock($resource, 500); + $redLock->unlock($gateB); + $this->assertInternalType('array', $gateB); + } + + public function testMultipleOk() + { + $redLock = new \RedLock\RedLock($this->servers); + $resource = 'redLock.key'; + + $gateA = $redLock->lock($resource, 1000); + $gateB = $redLock->lock($resource, 1000); + $gateC = $redLock->lock($resource, 1000); + $redLock->unlock($gateA); + + // Asserts + $this->assertInternalType('array', $gateA); + $this->assertFalse($gateB); + $this->assertFalse($gateC); + + $gateB = $redLock->lock($resource, 1000); + $gateC = $redLock->lock($resource, 1000); + $gateA = $redLock->lock($resource, 1000); + $redLock->unlock($gateB); + + // Asserts + $this->assertInternalType('array', $gateB); + $this->assertFalse($gateC); + $this->assertFalse($gateA); + + $gateC = $redLock->lock($resource, 1000); + $gateA = $redLock->lock($resource, 1000); + $gateB = $redLock->lock($resource, 1000); + $redLock->unlock($gateC); + + // Asserts + $this->assertInternalType('array', $gateC); + $this->assertFalse($gateA); + $this->assertFalse($gateB); + } +} \ No newline at end of file diff --git a/tests/RedLockTestListener.php b/tests/RedLockTestListener.php new file mode 100644 index 0000000..bae8eee --- /dev/null +++ b/tests/RedLockTestListener.php @@ -0,0 +1,61 @@ +getName()) + { + return; + } + // Start the Redis servers + passthru(sprintf('redis-server --port %d --daemonize yes', RedLockTest::SERVER_PORT_A)); + passthru(sprintf('redis-server --port %d --daemonize yes', RedLockTest::SERVER_PORT_B)); + passthru(sprintf('redis-server --port %d --daemonize yes', RedLockTest::SERVER_PORT_C)); + } + + public function endTestSuite(PHPUnit_Framework_TestSuite $suite) + { + if('RedLock Test Suite' != $suite->getName()) + { + return; + } + // Stop the Redis servers + passthru(sprintf('redis-cli -p %d shutdown', RedLockTest::SERVER_PORT_A)); + passthru(sprintf('redis-cli -p %d shutdown', RedLockTest::SERVER_PORT_B)); + passthru(sprintf('redis-cli -p %d shutdown', RedLockTest::SERVER_PORT_C)); + } + + public function startTest(PHPUnit_Framework_Test $test) + { + } + + public function endTest(PHPUnit_Framework_Test $test, $time) + { + } + + public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) + { + } + + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) + { + } + + public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) + { + } + + public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) + { + } + + public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) + { + } +} \ No newline at end of file diff --git a/tests/test1.php b/tests/test1.php deleted file mode 100644 index 9582ae1..0000000 --- a/tests/test1.php +++ /dev/null @@ -1,21 +0,0 @@ -lock('test', 10000); - - if ($lock) { - print_r($lock); - } else { - print "Lock not acquired\n"; - } -} From 6b0d5c509ab397dbf052039085943bd7237764dd Mon Sep 17 00:00:00 2001 From: Peter Scopes Date: Wed, 28 Jun 2017 15:41:58 +0100 Subject: [PATCH 4/4] Updating composer.json to allow for newer redis extensions --- composer.json | 2 +- phpunit.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 03b45ec..0461884 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "php": "~5.4", - "ext-redis": "~2.2.5" + "ext-redis": "~2.2.5|~3.1.2" }, "require-dev": { "php": "~5.6", diff --git a/phpunit.xml b/phpunit.xml index 9b0f5ed..87f70c3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,6 @@ stopOnIncomplete="false" stopOnSkipped="false" beStrictAboutTestsThatDoNotTestAnything="true" - checkForUnintentionallyCoveredCode="true" verbose="true" bootstrap="./vendor/autoload.php">