Repository: davedevelopment/stiphle Branch: master Commit: 5d1c244f0525 Files: 22 Total size: 38.8 KB Directory structure: gitextract_npjvi3hh/ ├── .gitignore ├── LICENCE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src/ │ └── Stiphle/ │ ├── Storage/ │ │ ├── Apc.php │ │ ├── Apcu.php │ │ ├── DoctrineCache.php │ │ ├── LockWaitTimeoutException.php │ │ ├── Memcached.php │ │ ├── Process.php │ │ ├── Redis.php │ │ ├── StorageInterface.php │ │ └── ZendStorage.php │ └── Throttle/ │ ├── LeakyBucket.php │ ├── ThrottleInterface.php │ └── TimeWindow.php └── tests/ └── src/ └── Stiphle/ ├── Storage/ │ ├── ApcTest.php │ ├── ProcessTest.php │ └── RedisTest.php └── Throttle/ ├── LeakyBucketTest.php └── TimeWindowTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /vendor /phpunit.xml /composer.lock /.idea/ ================================================ FILE: LICENCE ================================================ Copyright (C) 2012 Dave Marshall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Stiphle ====== Install via Composer ------- ``` composer require davedevelopment/stiphle ``` What is it? ----------- Stiphle is a little library to try and provide an easy way of throttling/rate limit requests, for those without fancy hardware etc. How does it work? ----------------- You create a throttle, and ask it how long you should wait. For example, given that $identifier is some means of identifying whatever it is you're throttling, and you want to throttle it to 5 requests per second: ``` php throttle($identifier, 5, 1000); } # 0 0 0 0 0 200 200.... ``` Use combinations of values to provide bursting etc, though use carefully as it screws with your mind ``` php throttle($identifier, 5, 1000); echo " b:" . $throttle->throttle($identifier, 20, 60000); echo "\n"; } #a:0 b:0 #a:0 b:0 #a:0 b:0 #a:0 b:0 #a:0 b:0 #a:199 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:0 #a:199 b:0 #a:200 b:2600 #a:0 b:3000 #a:0 b:2999 ``` Throttle Strategies ------------------- There are currently two types of throttles, [Leaky Bucket](http://en.wikipedia.org/wiki/Leaky_bucket) and a simple fixed time window. ``` php /** * Throttle to 1000 per *rolling* 24 hours, e.g. the counter will not reset at * midnight */ $throttle = new Stiphle\Throttle\LeakyBucket; $throttle->throttle('api.request', 1000, 86400000); /** * Throttle to 1000 per calendar day, counter will reset at midnight */ $throttle = new Stiphle\Throttle\TimeWindow; $throttle->throttle('api.request', 1000, 86400000); ``` __NB:__ The current implementation of the `TimeWindow` throttle will only work on 64-bit architectures! Storage ------- Stiphle currently ships with 5 storage engines * In process * APC * Memcached * Doctrine Cache * Redis Stiphle uses the in process storage by default. A different storage engine can be injected after object creation. ``` php $throttle = new Stiphle\Throttle\LeakyBucket(); $storage = new \Stiphle\Storage\Memcached(new \Memcached()); $throttle->setStorage($storage); ``` Todo ---- * More Tests! * Decent *Unit* tests * More throttling methods * More storage adapters, the current ones are a little volatile, Mongo, Cassandra, MemcacheDB etc Copyright --------- Copyright (c) 2011 Dave Marshall. See LICENCE for further details ================================================ FILE: composer.json ================================================ { "name": "davedevelopment/stiphle", "type": "library", "description": "Simple rate limiting/throttling for php", "keywords": ["throttle", "throttling", "rate limiting", "rate limit"], "homepage": "http://github.com/davedevelopment/stiphle", "license": "MIT", "authors": [{ "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", "homepage": "http://davedevelopment.co.uk" }], "require": { "php": "^5.6.0|^7.0|^8.0" }, "suggest": { "doctrine/cache": "~1.0", "predis/predis": "~1.1", "zendframework/zend-cache": "^2.8" }, "autoload": { "psr-0": { "Stiphle": "src/" } }, "require-dev": { "phpunit/phpunit": "^6.5|^7.5|^8.4", "predis/predis": "^1.1", "doctrine/cache": "^1.0", "zendframework/zend-cache": "^2.8" } } ================================================ FILE: phpunit.xml.dist ================================================ ./tests src ================================================ FILE: src/Stiphle/Storage/Apc.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Use Apc as the storage, I hope apc_add is atomic and therefore we wont get * any race conditions with the locking.... * * @author Dave Marshall */ class Apc implements StorageInterface { /** * @var int */ protected $lockWaitTimeout = 1000; /** * @var int Time to sleep when attempting to get lock in microseconds */ protected $sleep = 100; /** * @var int */ protected $ttl = 10000000; /** * Set lock wait timeout * * @param int $milliseconds */ public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; return; } /** * Set the sleep time in microseconds * * @param int * @return void */ public function setSleep($microseconds) { $this->sleep = $microseconds; return; } /** * Set the ttl for the apc records in seconds * * @param int $seconds * @return void */ public function setTtl($microseconds) { $this->ttl = $microseconds; return; } /** * Lock * * If we're using storage, we might have multiple requests coming in at * once, so we lock the storage * * @return void */ public function lock($key) { $key = $key . "::LOCK"; $start = microtime(true); while(!apc_add($key, true, $this->ttl)) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep($this->sleep); } return; } /** * Unlock * * @return void */ public function unlock($key) { $key = $key . "::LOCK"; apc_delete($key); } /** * Get last modified * * @param string $key * @return int */ public function get($key) { return apc_fetch($key); } /** * set * * @param string $key * @param mixed $value * @return void */ public function set($key, $value) { apc_store($key, $value, $this->ttl); return; } } ================================================ FILE: src/Stiphle/Storage/Apcu.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Apcu implements StorageInterface { /** * @var int */ protected $lockWaitTimeout = 1000; /** * @var int Time to sleep when attempting to get lock in microseconds */ protected $sleep = 100; /** * @var int */ protected $ttl = 10000000; /** * Set lock wait timeout * * @param int $milliseconds */ public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; return; } /** * Set the sleep time in microseconds * * @param int * @return void */ public function setSleep($microseconds) { $this->sleep = $microseconds; return; } /** * Set the ttl for the apc records in seconds * * @param int $seconds * @return void */ public function setTtl($microseconds) { $this->ttl = $microseconds; return; } /** * Lock * * If we're using storage, we might have multiple requests coming in at * once, so we lock the storage * * @return void */ public function lock($key) { $key = $key . "::LOCK"; $start = microtime(true); while(!apcu_add($key, true, $this->ttl)) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep($this->sleep); } return; } /** * Unlock * * @return void */ public function unlock($key) { $key = $key . "::LOCK"; apcu_delete($key); } /** * Get last modified * * @param string $key * @return int */ public function get($key) { return apcu_fetch($key); } /** * set * * @param string $key * @param mixed $value * @return void */ public function set($key, $value) { apcu_store($key, $value, $this->ttl); return; } } ================================================ FILE: src/Stiphle/Storage/DoctrineCache.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class DoctrineCache implements StorageInterface { /** @var Cache */ protected $cache; /** @var int */ protected $lockWaitTimeout; /** @var int */ protected $lockWaitInterval; public function __construct(Cache $cache, $lockWaitTimeout = 1000, $lockWaitInterval = 100) { $this->cache = $cache; $this->lockWaitTimeout = $lockWaitTimeout; $this->lockWaitInterval = $lockWaitInterval; } public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; } public function setSleep($microseconds) { $this->lockWaitInterval = $microseconds; } public function lock($key) { $key = $key . "::LOCK"; $start = microtime(true); while ($this->cache->contains($key)) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep($this->lockWaitInterval); } $this->cache->save($key, true); } public function unlock($key) { $key = $key . "::LOCK"; $this->cache->delete($key); } public function get($key) { return $this->cache->fetch($key); } public function set($key, $value) { $this->cache->save($key, $value); } } ================================================ FILE: src/Stiphle/Storage/LockWaitTimeoutException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Thrown when a request for a lock timesout * * @author Dave Marshall */ class LockWaitTimeoutException extends \Exception {} ================================================ FILE: src/Stiphle/Storage/Memcached.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Use memcached via PHP's memcached extension * * @author Dave Marshall */ class Memcached implements StorageInterface { /** * @var int */ protected $lockWaitTimeout = 1000; /** * @var int Time to sleep when attempting to get lock in microseconds */ protected $sleep = 100; /** * @var int */ protected $ttl = 3600; /** * Memcached instance */ protected $memcached; /** * Constructor * */ public function __construct(\Memcached $memcached) { $this->memcached = $memcached; } /** * Set lock wait timeout * * @param int $milliseconds */ public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; return; } /** * Set the sleep time in microseconds * * @param int * @return void */ public function setSleep($microseconds) { $this->sleep = $microseconds; return; } /** * Set the ttl for the apc records in seconds * * @param int $seconds * @return void */ public function setTtl($microseconds) { $this->ttl = $microseconds; return; } /** * Lock * * If we're using storage, we might have multiple requests coming in at * once, so we lock the storage * * @return void */ public function lock($key) { $key = $key . "::LOCK"; $start = microtime(true); while(!$this->memcached->add($key, true, $this->ttl)) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep($this->sleep); } return; } /** * Unlock * * @return void */ public function unlock($key) { $key = $key . "::LOCK"; $this->memcached->delete($key); } /** * Get last modified * * @param string $key * @return int */ public function get($key) { return $this->memcached->get($key); } /** * set * * @param string $key * @param mixed $value * @return void */ public function set($key, $value) { $this->memcached->set($key, $value, $this->ttl); return; } } ================================================ FILE: src/Stiphle/Storage/Process.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Basic in-process storage of values * * @author Dave Marshall */ class Process implements StorageInterface { /** * @var int */ protected $lockWaitTimeout = 1000; /** * @var array */ protected $locked = array(); /** * @var array */ protected $values = array(); /** * Set lock wait timeout * * @param int $milliseconds */ public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; } /** * Lock * * If we're using storage, we might have multiple requests coming in at * once, so we lock the storage * * @return void */ public function lock($key) { if (!isset($this->locked[$key])) { $this->locked[$key] = false; } $start = microtime(true); while($this->locked[$key]) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } } $this->locked[$key] = true; return; } /** * Unlock * * @return void */ public function unlock($key) { $this->locked[$key] = false; } /** * Get * * @param string $key * @return int */ public function get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } return null; } /** * set * * @param string $key * @param mixed $value * @return void */ public function set($key, $value) { $this->values[$key] = $value; } } ================================================ FILE: src/Stiphle/Storage/Redis.php ================================================ */ class Redis implements StorageInterface { protected $lockWaitTimeout = 1000; protected $redisClient; public function __construct(\Predis\Client $redisClient) { $this->redisClient = $redisClient; } /** * {@inheritDoc} */ public function setLockWaitTimeout($milliseconds) { $this->lockWaitTimeout = $milliseconds; } /** * {@inheritDoc} */ public function lock($key) { $start = microtime(true); while (is_null($this->redisClient->set($this->getLockKey($key), 'LOCKED', 'PX', 3600, 'NX'))) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep(100); } } /** * {@inheritDoc} */ public function unlock($key) { $this->redisClient->del($this->getLockKey($key)); } /** * {@inheritDoc} */ public function get($key) { return $this->redisClient->get($key); } /** * {@inheritDoc} */ public function set($key, $value) { $this->redisClient->set($key, $value); } private function getLockKey($key) { return $key . "::LOCK"; } } ================================================ FILE: src/Stiphle/Storage/StorageInterface.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface describing a persistant storage mechanism for the LeakyBucket * throttle * * @author Dave Marshall */ interface StorageInterface { /** * Set lock wait timout * * @param int $milliseconds */ public function setLockWaitTimeout($milliseconds); /** * Lock * * We might have multiple requests coming in at once, so we lock the storage * * @return void */ public function lock($key); /** * Unlock * * @return void */ public function unlock($key); /** * Get * * @param string $key * @return int */ public function get($key); /** * set last modified * * @param string $key * @param mixed $value * @return void */ public function set($key, $value); } ================================================ FILE: src/Stiphle/Storage/ZendStorage.php ================================================ cache = $cache; $this->lockWaitTimeout = $lockWaitTimeout; $this->lockWaitInterval = $lockWaitInterval; } public function setLockWaitTimeout($lockWaitTimeout) { $this->lockWaitTimeout = $lockWaitTimeout; } public function lock($key) { $key = sprintf('%s::LOCK', $key); $start = microtime(true); while ($this->cache->hasItem($key)) { $passed = (microtime(true) - $start) * 1000; if ($passed > $this->lockWaitTimeout) { throw new LockWaitTimeoutException(); } usleep($this->lockWaitInterval); } $this->cache->setItem($key, true); } public function unlock($key) { $key = sprintf('%s::LOCK', $key); $this->cache->removeItem($key); } public function get($key) { return $this->cache->getItem($key); } public function set($key, $value) { $this->cache->setItem($key, $value); } } ================================================ FILE: src/Stiphle/Throttle/LeakyBucket.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A 'leaky bucket' style rate limiter * * @see http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users * @author Dave Marshall */ class LeakyBucket implements ThrottleInterface { /** * @var StorageInterface */ protected $storage; /** * */ public function __construct() { $this->storage = new Process(); } /** * Throttle * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int * @throws LockWaitTimeoutException */ public function throttle($key, $limit, $milliseconds) { /** * Try and do our waiting without a lock */ $key = $this->getStorageKey($key, $limit, $milliseconds); $wait = 0; $newRatio = $this->getNewRatio($key, $limit, $milliseconds); if ($newRatio > $milliseconds) { $wait = ceil($newRatio - $milliseconds); } usleep($wait * 1000); /** * Lock, record and release */ $this->storage->lock($key); $newRatio = $this->getNewRatio($key, $limit, $milliseconds); $this->setLastRatio($key, $newRatio); $this->setLastRequest($key, microtime(1)); $this->storage->unlock($key); return $wait; } /** * Get Estimate (doesn't require lock) * * How long would I have to wait to make a request? * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int - the number of milliseconds before this request should be allowed * to pass */ public function getEstimate($key, $limit, $milliseconds) { $key = $this->getStorageKey($key, $limit, $milliseconds); $newRatio = $this->getNewRatio($key, $limit, $milliseconds); $wait = 0; if ($newRatio > $milliseconds) { $wait = ceil($newRatio - $milliseconds); } return $wait; } /** * Get new ratio * * Assuming we're making a request, get the ratio of requests made to * requests allowed * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return float */ protected function getNewRatio($key, $limit, $milliseconds) { $lastRequest = $this->getLastRequest($key) ?: 0; $lastRatio = $this->getLastRatio($key) ?: 0; $diff = (microtime(1) - $lastRequest) * 1000; $newRatio = $lastRatio - $diff; $newRatio = $newRatio < 0 ? 0 : $newRatio; $newRatio+= $milliseconds/$limit; return $newRatio; } /** * Get storage key * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return string */ protected function getStorageKey($key, $limit, $milliseconds) { return $key . '::' . $limit . '::' . $milliseconds; } /** * Set Storage * * @param StorageInterface $storage * @return LeakyBucket */ public function setStorage(StorageInterface $storage) { $this->storage = $storage; return $this; } /** * Get Last Ratio * * @param string $key * @return float */ protected function getLastRatio($key) { return $this->storage->get($key . '::LASTRATIO'); } /** * Set Last Ratio * * @param string $key * @param float $ratio * @return void */ protected function setLastRatio($key, $ratio) { $this->storage->set($key . '::LASTRATIO', $ratio); } /** * Get Last Request * * @param string $key * @return float */ protected function getLastRequest($key) { return $this->storage->get($key . '::LASTREQUEST'); } /** * Set Last Request * * @param string $key * @param float $request * @return void */ protected function setLastRequest($key, $request) { $this->storage->set($key . '::LASTREQUEST', $request); } } ================================================ FILE: src/Stiphle/Throttle/ThrottleInterface.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface describing a throttle * * @author Dave Marshall */ interface ThrottleInterface { /** * Throttle * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int * @throws LockWaitTimeoutException */ public function throttle($key, $limit, $milliseconds); /** * Get Estimate * * If I were to throttle now, how long would I be waiting * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int - the number of milliseconds before this request should be allowed */ public function getEstimate($key, $limit, $milliseconds); } ================================================ FILE: src/Stiphle/Throttle/TimeWindow.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A throttle based on a fixed time window * * @author Dave Marshall */ class TimeWindow implements ThrottleInterface { /** * @var StorageInterface */ protected $storage; /** * */ public function __construct() { $this->storage = new Process(); } /** * Throttle * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int * @throws LockWaitTimeoutException */ public function throttle($key, $limit, $milliseconds) { /** * Try do our waiting without a lock, so may sneak through because of * this... */ $wait = $this->getEstimate($key, $limit, $milliseconds); if ($wait > 0) { usleep($wait * 1000); } $key = $this->getStorageKey($key, $limit, $milliseconds); $this->storage->lock($key); $count = $this->storage->get($key); $count++; $this->storage->set($key, $count); $this->storage->unlock($key); return $wait; } /** * Get Estimate (doesn't require lock) * * How long would I have to wait to make a request? * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return int - the number of milliseconds before this request should be allowed * to pass */ public function getEstimate($key, $limit, $milliseconds) { $key = $this->getStorageKey($key, $limit, $milliseconds); $count = $this->storage->get($key); if ($count < $limit) { return 0; } return $milliseconds - ((microtime(1) * 1000) % (float) $milliseconds); } /** * Get storage key * * @param string $key - A unique key for what we're throttling * @param int $limit - How many are allowed * @param int $milliseconds - In this many milliseconds * @return string */ protected function getStorageKey($key, $limit, $milliseconds) { $window = $milliseconds * (floor((microtime(1) * 1000)/$milliseconds)); $date = date('YmdHis', $window/1000); return $date . '::' . $key . '::' . $limit . '::' . $milliseconds . '::COUNT'; } /** * Set Storage * * @param StorageInterface $storage * @return TimeWindow */ public function setStorage(StorageInterface $storage) { $this->storage = $storage; return $this; } } ================================================ FILE: tests/src/Stiphle/Storage/ApcTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * TITLE * * DESCRIPTION * * @author Dave Marshall */ class ApcTest extends PHPUnit_Framework_TestCase { protected $storage = null; public function setup() { $this->storage = new Apc(); } public function tearDown() { apc_delete('dave::LOCK'); } /** * @expectedException Stiphle\Storage\LockWaitTimeoutException */ public function testLockThrowsLockWaitTimeoutException() { if (!ini_get('apc.enable_cli') && !ini_get('apcu.enable_cli')) { $this->markTestSkipped('APC and APCu needs enabling for the cli via apc.enable_cli=1 or apcu.enable_cli=1'); } $this->storage->lock('dave'); $this->storage->lock('dave'); } public function testLockRespectsLockWaitTimeoutValue() { if (!ini_get('apc.enable_cli') && !ini_get('apcu.enable_cli')) { $this->markTestSkipped('APC and APCu needs enabling for the cli via apc.enable_cli=1 or apcu.enable_cli=1'); } /** * Test we can do this */ $this->storage->lock('dave'); try { $start = microtime(1); $this->storage->lock('dave'); } catch (LockWaitTimeoutException $e) { $caught = microtime(1); $diff = $caught - $start; if (round($diff) != 1) { $this->markTestSkipped("Don't think the timings will be accurate enough, expected exception after 1 second, was $diff"); } } $this->storage->setLockWaitTimeout(2000); try { $start = microtime(1); $this->storage->lock('dave'); $this->fail("should not get to this point"); } catch (LockWaitTimeoutException $e) { $caught = microtime(1); $diff = $caught - $start; $this->assertEquals(2, round($diff), "Exception thrown after approximately 2000 milliseconds"); } } } ================================================ FILE: tests/src/Stiphle/Storage/ProcessTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * TITLE * * DESCRIPTION * * @author Dave Marshall */ class ProcessTest extends PHPUnit_Framework_TestCase { protected $storage = null; public function setup() { $this->storage = new Process(); } /** * @expectedException Stiphle\Storage\LockWaitTimeoutException */ public function testLockThrowsLockWaitTimeoutException() { $this->storage->lock('dave'); $this->storage->lock('dave'); } public function testLockRespectsLockWaitTimeoutValue() { /** * Test we can do this */ $this->storage->lock('dave'); try { $start = microtime(1); $this->storage->lock('dave'); } catch (LockWaitTimeoutException $e) { $caught = microtime(1); $diff = $caught - $start; if (round($diff) != 1) { $this->markTestSkipped("Don't think the timings will be accurate enough, expected exception after 1 second, was $diff"); } } $this->storage->setLockWaitTimeout(2000); try { $start = microtime(1); $this->storage->lock('dave'); $this->fail("should not get to this point"); } catch (LockWaitTimeoutException $e) { $caught = microtime(1); $diff = $caught - $start; $this->assertEquals(2, round($diff), "Exception thrown after approximately 2000 milliseconds"); } } } ================================================ FILE: tests/src/Stiphle/Storage/RedisTest.php ================================================ getMockBuilder(\Predis\Client::class) ->setMethods(['set']) ->getMock(); $redisClient->expects($this->at(0)) ->method('set') ->with('dave::LOCK', 'LOCKED', 'PX', 3600, 'NX') ->will($this->returnValue(1)); $redisClient->expects($this->any()) ->method('set') ->with('dave::LOCK', 'LOCKED', 'PX', 3600, 'NX') ->will($this->returnValue(null)); $this->expectException(\Stiphle\Storage\LockWaitTimeoutException::class); $storage = new Redis($redisClient); $storage->lock('dave'); $storage->lock('dave'); } public function testStorageCanBeUnlocked() { $redisClient = $this->getMockBuilder(\Predis\Client::class) ->setMethods(['del']) ->getMock(); $redisClient->expects($this->once()) ->method('del') ->with('dave::LOCK'); $storage = new Redis($redisClient); $storage->unlock('dave'); } } ================================================ FILE: tests/src/Stiphle/Throttle/LeakyBucketTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * TITLE * * DESCRIPTION * * @author Dave Marshall */ class LeakyBucketTest extends PHPUnit_Framework_TestCase { /** @var LeakyBucket */ protected $throttle; public function setup() { $this->throttle = new LeakyBucket(); } /** * This test assumes your machine is capable of processing the first five * calls in less that a second :) * * Nothing special here, ideally we need to mock the storage out and test it * with different values etc */ public function testGetEstimate() { $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000)); $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000)); $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000)); $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000)); $this->assertEquals(0, $this->throttle->throttle('dave', 5, 1000)); $this->assertGreaterThan(0, $this->throttle->getEstimate('dave', 5, 1000)); $this->assertGreaterThan(0, $this->throttle->throttle('dave', 5, 1000)); } } ================================================ FILE: tests/src/Stiphle/Throttle/TimeWindowTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * TITLE * * DESCRIPTION * * @author Dave Marshall */ class TimeWindowTest extends PHPUnit_Framework_TestCase { /** @var TimeWindow */ protected $throttle; public function setup() { $this->throttle = new TimeWindow(); } /** * Really crap test here, without mocking the system time, it's difficult to * know when you're going to throttled... */ public function testGetEstimate() { $timeout = strtotime('+5 seconds', microtime(1)); $count = 0; while (microtime(1) < $timeout) { $wait = $this->throttle->throttle('dave', 5, 1000); if (microtime(1) < $timeout) { $count++; } } $this->assertEquals(25, $count); } }