added redis contrib module
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_AbstractBackend implements Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Key components name separator
|
||||
*/
|
||||
const KEY_SEPARATOR = ':';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param mixed $client
|
||||
* Redis client
|
||||
* @param string $namespace
|
||||
* Component namespace
|
||||
* @param string $prefix
|
||||
* Component prefix
|
||||
*/
|
||||
public function __construct($client, $namespace = null, $prefix = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->prefix = $prefix;
|
||||
|
||||
if (null !== $namespace) {
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
final public function setClient($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
final public function getClient()
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
final public function setPrefix($prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
final public function getPrefix()
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
final public function setNamespace($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
final public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefixed key
|
||||
*
|
||||
* @param string|string[] $parts
|
||||
* Arbitrary number of strings to compose the key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey($parts = array())
|
||||
{
|
||||
$key = array();
|
||||
|
||||
if (null !== $this->prefix) {
|
||||
$key[] = $this->prefix;
|
||||
}
|
||||
if (null !== $this->namespace) {
|
||||
$key[] = $this->namespace;
|
||||
}
|
||||
|
||||
if ($parts) {
|
||||
if (is_array($parts)) {
|
||||
foreach ($parts as $part) {
|
||||
if ($part) {
|
||||
$key[] = $part;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$key[] = $parts;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(self::KEY_SEPARATOR, array_filter($key));
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client based Redis component
|
||||
*/
|
||||
interface Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Set client
|
||||
*
|
||||
* @param mixed $client
|
||||
*/
|
||||
public function setClient($client);
|
||||
|
||||
/**
|
||||
* Get client
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getClient();
|
||||
|
||||
/**
|
||||
* Set prefix
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function setPrefix($prefix);
|
||||
|
||||
/**
|
||||
* Get prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefix();
|
||||
|
||||
/**
|
||||
* Set namespace
|
||||
*
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function setNamespace($namespace);
|
||||
|
||||
/**
|
||||
* Get namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace();
|
||||
|
||||
/**
|
||||
* Get full key name using the set prefix
|
||||
*
|
||||
* @param string ...
|
||||
* Any numer of strings to append to path using the separator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey();
|
||||
}
|
655
sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php
Normal file
655
sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php
Normal file
@@ -0,0 +1,655 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Because those objects will be spawned during boostrap all its configuration
|
||||
* must be set in the settings.php file.
|
||||
*
|
||||
* You will find the driver specific implementation in the Redis_Cache_*
|
||||
* classes as they may differ in how the API handles transaction, pipelining
|
||||
* and return values.
|
||||
*/
|
||||
class Redis_Cache
|
||||
implements DrupalCacheInterface
|
||||
{
|
||||
/**
|
||||
* Default lifetime for permanent items.
|
||||
* Approximatively 1 year.
|
||||
*/
|
||||
const LIFETIME_PERM_DEFAULT = 31536000;
|
||||
|
||||
/**
|
||||
* Uses EVAL scripts to flush data when called
|
||||
*
|
||||
* This remains the default behavior and is safe until you use a single
|
||||
* Redis server instance and its version is >= 2.6 (older version don't
|
||||
* support EVAL).
|
||||
*/
|
||||
const FLUSH_NORMAL = 0;
|
||||
|
||||
/**
|
||||
* This mode is tailored for sharded Redis servers instances usage: it
|
||||
* will never delete entries but only mark the latest flush timestamp
|
||||
* into one of the servers in the shard. It will proceed to delete on
|
||||
* read single entries when invalid entries are being loaded.
|
||||
*/
|
||||
const FLUSH_SHARD = 3;
|
||||
|
||||
/**
|
||||
* Same as the one above, plus attempt to do pipelining when possible.
|
||||
*
|
||||
* This is supposed to work with sharding proxies that supports
|
||||
* pipelining themselves, such as Twemproxy.
|
||||
*/
|
||||
const FLUSH_SHARD_WITH_PIPELINING = 4;
|
||||
|
||||
/**
|
||||
* Computed keys are let's say arround 60 characters length due to
|
||||
* key prefixing, which makes 1,000 keys DEL command to be something
|
||||
* arround 50,000 bytes length: this is huge and may not pass into
|
||||
* Redis, let's split this off.
|
||||
* Some recommend to never get higher than 1,500 bytes within the same
|
||||
* command which makes us forced to split this at a very low threshold:
|
||||
* 20 seems a safe value here (1,280 average length).
|
||||
*/
|
||||
const KEY_THRESHOLD = 20;
|
||||
|
||||
/**
|
||||
* @var Redis_Cache_BackendInterface
|
||||
*/
|
||||
protected $backend;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bin;
|
||||
|
||||
/**
|
||||
* When the global 'cache_lifetime' Drupal variable is set to a value, the
|
||||
* cache backends should not expire temporary entries by themselves per
|
||||
* Drupal signature. Volatile items will be dropped accordingly to their
|
||||
* set lifetime.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $allowTemporaryFlush = true;
|
||||
|
||||
/**
|
||||
* When in shard mode, the backend cannot proceed to multiple keys
|
||||
* operations, and won't delete keys on flush calls.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isSharded = false;
|
||||
|
||||
/**
|
||||
* When in shard mode, the proxy may or may not support pipelining,
|
||||
* Twemproxy is known to support it.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $allowPipeline = false;
|
||||
|
||||
/**
|
||||
* Default TTL for CACHE_PERMANENT items.
|
||||
*
|
||||
* See "Default lifetime for permanent items" section of README.txt
|
||||
* file for a comprehensive explaination of why this exists.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $permTtl = self::LIFETIME_PERM_DEFAULT;
|
||||
|
||||
/**
|
||||
* Maximum TTL for this bin from Drupal configuration.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxTtl = 0;
|
||||
|
||||
/**
|
||||
* Flush permanent and volatile cached values
|
||||
*
|
||||
* @var string[]
|
||||
* First value is permanent latest flush time and second value
|
||||
* is volatile latest flush time
|
||||
*/
|
||||
protected $flushCache = null;
|
||||
|
||||
/**
|
||||
* Is this bin in shard mode
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSharded()
|
||||
{
|
||||
return $this->isSharded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this bin allow pipelining through sharded environment
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function allowPipeline()
|
||||
{
|
||||
return $this->allowPipeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this bin allow temporary item flush
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function allowTemporaryFlush()
|
||||
{
|
||||
return $this->allowTemporaryFlush;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TTL for CACHE_PERMANENT items.
|
||||
*
|
||||
* @return int
|
||||
* Lifetime in seconds.
|
||||
*/
|
||||
public function getPermTtl()
|
||||
{
|
||||
return $this->permTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum TTL for all items.
|
||||
*
|
||||
* @return int
|
||||
* Lifetime in seconds.
|
||||
*/
|
||||
public function getMaxTtl()
|
||||
{
|
||||
return $this->maxTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($bin)
|
||||
{
|
||||
$this->bin = $bin;
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_CACHE);
|
||||
$this->backend = new $className(Redis_Client::getClient(), $bin, Redis_Client::getDefaultPrefix($bin));
|
||||
|
||||
$this->refreshCapabilities();
|
||||
$this->refreshPermTtl();
|
||||
$this->refreshMaxTtl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the clear mode.
|
||||
*/
|
||||
public function refreshCapabilities()
|
||||
{
|
||||
if (0 < variable_get('cache_lifetime', 0)) {
|
||||
// Per Drupal default behavior, when the 'cache_lifetime' variable
|
||||
// is set we must not flush any temporary items since they have a
|
||||
// life time.
|
||||
$this->allowTemporaryFlush = false;
|
||||
}
|
||||
|
||||
if (null !== ($mode = variable_get('redis_flush_mode', null))) {
|
||||
$mode = (int)$mode;
|
||||
} else {
|
||||
$mode = self::FLUSH_NORMAL;
|
||||
}
|
||||
|
||||
$this->isSharded = self::FLUSH_SHARD === $mode || self::FLUSH_SHARD_WITH_PIPELINING === $mode;
|
||||
$this->allowPipeline = self::FLUSH_SHARD !== $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the right permanent items TTL.
|
||||
*/
|
||||
protected function refreshPermTtl()
|
||||
{
|
||||
$ttl = null;
|
||||
if (null === ($ttl = variable_get('redis_perm_ttl_' . $this->bin, null))) {
|
||||
if (null === ($ttl = variable_get('redis_perm_ttl', null))) {
|
||||
$ttl = self::LIFETIME_PERM_DEFAULT;
|
||||
}
|
||||
}
|
||||
if ($ttl === (int)$ttl) {
|
||||
$this->permTtl = $ttl;
|
||||
} else {
|
||||
if ($iv = DateInterval::createFromDateString($ttl)) {
|
||||
// http://stackoverflow.com/questions/14277611/convert-dateinterval-object-to-seconds-in-php
|
||||
$this->permTtl = ($iv->y * 31536000 + $iv->m * 2592000 + $iv->d * 86400 + $iv->h * 3600 + $iv->i * 60 + $iv->s);
|
||||
} else {
|
||||
// Sorry but we have to log this somehow.
|
||||
trigger_error(sprintf("Parsed TTL '%s' has an invalid value: switching to default", $ttl));
|
||||
$this->permTtl = self::LIFETIME_PERM_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the maximum cache lifetime.
|
||||
*/
|
||||
public function refreshMaxTtl()
|
||||
{
|
||||
// And now cache lifetime. Be aware we exclude negative values
|
||||
// considering those are Drupal misconfiguration.
|
||||
$maxTtl = variable_get('cache_lifetime', 0);
|
||||
if (0 < $maxTtl) {
|
||||
if ($maxTtl < $this->permTtl) {
|
||||
$this->maxTtl = $maxTtl;
|
||||
} else {
|
||||
$this->maxTtl = $this->permTtl;
|
||||
}
|
||||
} else if ($this->permTtl) {
|
||||
$this->maxTtl = $this->permTtl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set last flush time
|
||||
*
|
||||
* @param string $permanent
|
||||
* @param string $volatile
|
||||
*/
|
||||
public function setLastFlushTime($permanent = false, $volatile = false)
|
||||
{
|
||||
// Here we need to fetch absolute values from backend, to avoid
|
||||
// concurrency problems and ensure data validity.
|
||||
list($flushPerm, $flushVolatile) = $this->backend->getLastFlushTime();
|
||||
|
||||
$checksum = $this->getValidChecksum(
|
||||
max(array(
|
||||
$flushPerm,
|
||||
$flushVolatile,
|
||||
$permanent,
|
||||
time(),
|
||||
))
|
||||
);
|
||||
|
||||
if ($permanent) {
|
||||
$this->backend->setLastFlushTimeFor($checksum, false);
|
||||
$this->backend->setLastFlushTimeFor($checksum, true);
|
||||
$this->flushCache = array($checksum, $checksum);
|
||||
} else if ($volatile) {
|
||||
$this->backend->setLastFlushTimeFor($checksum, true);
|
||||
$this->flushCache = array($flushPerm, $checksum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest flush time
|
||||
*
|
||||
* @return string[]
|
||||
* First value is the latest flush time for permanent entries checksum,
|
||||
* second value is the latest flush time for volatile entries checksum.
|
||||
*/
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
if (!$this->flushCache) {
|
||||
$this->flushCache = $this->backend->getLastFlushTime();
|
||||
}
|
||||
|
||||
// At the very first hit, we might not have the timestamps set, thus
|
||||
// we need to create them to avoid our entry being considered as
|
||||
// invalid
|
||||
if (!$this->flushCache[0]) {
|
||||
$this->setLastFlushTime(true, true);
|
||||
} else if (!$this->flushCache[1]) {
|
||||
$this->setLastFlushTime(false, true);
|
||||
}
|
||||
|
||||
return $this->flushCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cache entry
|
||||
*
|
||||
* @param string $cid
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
if (CACHE_TEMPORARY === $expire) {
|
||||
$validityThreshold = max(array($flushVolatile, $flushPerm));
|
||||
} else {
|
||||
$validityThreshold = $flushPerm;
|
||||
}
|
||||
|
||||
$time = $this->getValidChecksum($validityThreshold);
|
||||
|
||||
$hash = array(
|
||||
'cid' => $cid,
|
||||
'created' => $time,
|
||||
'expire' => $expire,
|
||||
);
|
||||
|
||||
// Let Redis handle the data types itself.
|
||||
if (!is_string($data)) {
|
||||
$hash['data'] = serialize($data);
|
||||
$hash['serialized'] = 1;
|
||||
} else {
|
||||
$hash['data'] = $data;
|
||||
$hash['serialized'] = 0;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand cache entry from fetched data
|
||||
*
|
||||
* @param array $values
|
||||
* Raw values fetched from Redis server data
|
||||
*
|
||||
* @return array
|
||||
* Or FALSE if entry is invalid
|
||||
*/
|
||||
protected function expandEntry(array $values, $flushPerm, $flushVolatile)
|
||||
{
|
||||
// Check for entry being valid.
|
||||
if (empty($values['cid'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This ensures backward compatibility with older version of
|
||||
// this module's data still stored in Redis.
|
||||
if (isset($values['expire'])) {
|
||||
$expire = (int)$values['expire'];
|
||||
// Ensure the entry is valid and have not expired.
|
||||
if ($expire !== CACHE_PERMANENT && $expire !== CACHE_TEMPORARY && $expire <= time()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the entry does not predate the last flush time.
|
||||
if ($this->allowTemporaryFlush && !empty($values['volatile'])) {
|
||||
$validityThreshold = max(array($flushPerm, $flushVolatile));
|
||||
} else {
|
||||
$validityThreshold = $flushPerm;
|
||||
}
|
||||
|
||||
if ($values['created'] <= $validityThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entry = (object)$values;
|
||||
|
||||
// Reduce the checksum to the real timestamp part
|
||||
$entry->created = (int)$entry->created;
|
||||
|
||||
if ($entry->serialized) {
|
||||
$entry->data = unserialize($entry->data);
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($cid)
|
||||
{
|
||||
$values = $this->backend->get($cid);
|
||||
|
||||
if (empty($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
$entry = $this->expandEntry($values, $flushPerm, $flushVolatile);
|
||||
|
||||
if (!$entry) { // This entry exists but is invalid.
|
||||
$this->backend->delete($cid);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(&$cids)
|
||||
{
|
||||
$ret = array();
|
||||
$delete = array();
|
||||
|
||||
if (!$this->allowPipeline) {
|
||||
$entries = array();
|
||||
foreach ($cids as $cid) {
|
||||
if ($entry = $this->backend->get($cid)) {
|
||||
$entries[$cid] = $entry;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$entries = $this->backend->getMultiple($cids);
|
||||
}
|
||||
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
foreach ($cids as $key => $cid) {
|
||||
if (!empty($entries[$cid])) {
|
||||
$entry = $this->expandEntry($entries[$cid], $flushPerm, $flushVolatile);
|
||||
} else {
|
||||
$entry = null;
|
||||
}
|
||||
if (empty($entry)) {
|
||||
$delete[] = $cid;
|
||||
} else {
|
||||
$ret[$cid] = $entry;
|
||||
unset($cids[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($delete)) {
|
||||
if ($this->allowPipeline) {
|
||||
foreach ($delete as $id) {
|
||||
$this->backend->delete($id);
|
||||
}
|
||||
} else {
|
||||
$this->backend->deleteMultiple($delete);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
$hash = $this->createEntryHash($cid, $data, $expire);
|
||||
$maxTtl = $this->getMaxTtl();
|
||||
|
||||
switch ($expire) {
|
||||
|
||||
case CACHE_PERMANENT:
|
||||
$this->backend->set($cid, $hash, $maxTtl, false);
|
||||
break;
|
||||
|
||||
case CACHE_TEMPORARY:
|
||||
$this->backend->set($cid, $hash, $maxTtl, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
$ttl = $expire - time();
|
||||
// Ensure $expire consistency
|
||||
if ($ttl <= 0) {
|
||||
// Entry has already expired, but we may have a stalled
|
||||
// older cache entry remaining there, ensure it wont
|
||||
// happen by doing a preventive delete
|
||||
$this->backend->delete($cid);
|
||||
} else {
|
||||
if ($maxTtl && $maxTtl < $ttl) {
|
||||
$ttl = $maxTtl;
|
||||
}
|
||||
$this->backend->set($cid, $hash, $ttl, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear($cid = null, $wildcard = false)
|
||||
{
|
||||
if (null === $cid && !$wildcard) {
|
||||
// Drupal asked for volatile entries flush, this will happen
|
||||
// during cron run, mostly
|
||||
$this->setLastFlushTime(false, true);
|
||||
|
||||
if (!$this->isSharded && $this->allowTemporaryFlush) {
|
||||
$this->backend->flushVolatile();
|
||||
}
|
||||
} else if ($wildcard) {
|
||||
if (empty($cid)) {
|
||||
// This seems to be an error, just do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
if ('*' === $cid) {
|
||||
// Use max() to ensure we invalidate both correctly
|
||||
$this->setLastFlushTime(true);
|
||||
|
||||
if (!$this->isSharded) {
|
||||
$this->backend->flush();
|
||||
}
|
||||
} else {
|
||||
if (!$this->isSharded) {
|
||||
$this->backend->deleteByPrefix($cid);
|
||||
} else {
|
||||
// @todo This needs a map algorithm the same way memcache
|
||||
// module implemented it for invalidity by prefixes. This
|
||||
// is a very stupid fallback
|
||||
$this->setLastFlushTime(true);
|
||||
}
|
||||
}
|
||||
} else if (is_array($cid)) {
|
||||
$this->backend->deleteMultiple($cid);
|
||||
} else {
|
||||
$this->backend->delete($cid);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* From the given timestamp build an incremental safe time-based identifier.
|
||||
*
|
||||
* Due to potential accidental cache wipes, when a server goes down in the
|
||||
* cluster or when a server triggers its LRU algorithm wipe-out, keys that
|
||||
* matches flush or tags checksum might be dropped.
|
||||
*
|
||||
* Per default, each new inserted tag will trigger a checksum computation to
|
||||
* be stored in the Redis server as a timestamp. In order to ensure a checksum
|
||||
* validity a simple comparison between the tag checksum and the cache entry
|
||||
* checksum will tell us if the entry pre-dates the current checksum or not,
|
||||
* thus telling us its state. The main problem we experience is that Redis
|
||||
* is being so fast it is able to create and drop entries at same second,
|
||||
* sometime even the same micro second. The only safe way to avoid conflicts
|
||||
* is to checksum using an arbitrary computed number (a sequence).
|
||||
*
|
||||
* Drupal core does exactly this thus tags checksums are additions of each tag
|
||||
* individual checksum; each tag checksum is a independent arbitrary serial
|
||||
* that gets incremented starting with 0 (no invalidation done yet) to n (n
|
||||
* invalidations) which grows over time. This way the checksum computation
|
||||
* always rises and we have a sensible default that works in all cases.
|
||||
*
|
||||
* This model works as long as you can ensure consistency for the serial
|
||||
* storage over time. Nevertheless, as explained upper, in our case this
|
||||
* serial might be dropped at some point for various valid technical reasons:
|
||||
* if we start over to 0, we may accidentally compute a checksum which already
|
||||
* existed in the past and make invalid entries turn back to valid again.
|
||||
*
|
||||
* In order to prevent this behavior, using a timestamp as part of the serial
|
||||
* ensures that we won't experience this problem in a time range wider than a
|
||||
* single second, which is safe enough for us. But using timestamp creates a
|
||||
* new problem: Redis is so fast that we can set or delete hundreds of entries
|
||||
* easily during the same second: an entry created then invalidated the same
|
||||
* second will create false positives (entry is being considered as valid) -
|
||||
* note that depending on the check algorithm, false negative may also happen
|
||||
* the same way. Therefore we need to have an abitrary serial value to be
|
||||
* incremented in order to enforce our checks to be more strict.
|
||||
*
|
||||
* The solution to both the first (the need for a time based checksum in case
|
||||
* of checksum data being dropped) and the second (the need to have an
|
||||
* arbitrary predictible serial value to avoid false positives or negatives)
|
||||
* we are combining the two: every checksum will be built this way:
|
||||
*
|
||||
* UNIXTIMESTAMP.SERIAL
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* 1429789217.017
|
||||
*
|
||||
* will reprensent the 17th invalidation of the 1429789217 exact second which
|
||||
* happened while writing this documentation. The next tag being invalidated
|
||||
* the same second will then have this checksum:
|
||||
*
|
||||
* 1429789217.018
|
||||
*
|
||||
* And so on...
|
||||
*
|
||||
* In order to make it consitent with PHP string and float comparison we need
|
||||
* to set fixed precision over the decimal, and store as a string to avoid
|
||||
* possible float precision problems when comparing.
|
||||
*
|
||||
* This algorithm is not fully failsafe, but allows us to proceed to 1000
|
||||
* operations on the same checksum during the same second, which is a
|
||||
* sufficiently great value to reduce the conflict probability to almost
|
||||
* zero for most uses cases.
|
||||
*
|
||||
* @param int|string $timestamp
|
||||
* "TIMESTAMP[.INCREMENT]" string
|
||||
*
|
||||
* @return string
|
||||
* The next "TIMESTAMP.INCREMENT" string.
|
||||
*/
|
||||
public function getNextIncrement($timestamp = null)
|
||||
{
|
||||
if (!$timestamp) {
|
||||
return time() . '.000';
|
||||
}
|
||||
|
||||
if (false !== ($pos = strpos($timestamp, '.'))) {
|
||||
$inc = substr($timestamp, $pos + 1, 3);
|
||||
|
||||
return ((int)$timestamp) . '.' . str_pad($inc + 1, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $timestamp . '.000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid checksum
|
||||
*
|
||||
* @param int|string $previous
|
||||
* "TIMESTAMP[.INCREMENT]" string
|
||||
*
|
||||
* @return string
|
||||
* The next "TIMESTAMP.INCREMENT" string.
|
||||
*
|
||||
* @see Redis_Cache::getNextIncrement()
|
||||
*/
|
||||
public function getValidChecksum($previous = null)
|
||||
{
|
||||
if (time() === (int)$previous) {
|
||||
return $this->getNextIncrement($previous);
|
||||
} else {
|
||||
return $this->getNextIncrement();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Real cache backend primitives. This functions will be used by the
|
||||
* Redis_Cache wrapper class that implements the high-level logic that
|
||||
* allows us to be Drupal compatible.
|
||||
*/
|
||||
interface Redis_Cache_BackendInterface extends Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Defaut constructor
|
||||
*
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function __construct($client, $namespace);
|
||||
|
||||
/**
|
||||
* Get namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace();
|
||||
|
||||
/**
|
||||
* Set last flush time
|
||||
*
|
||||
* @param int $time
|
||||
* @param boolean $volatile
|
||||
*/
|
||||
public function setLastFlushTimeFor($time, $volatile = false);
|
||||
|
||||
/**
|
||||
* Get last flush time
|
||||
*
|
||||
* @return int[]
|
||||
* First value is for non-volatile items, second value is for volatile items.
|
||||
*/
|
||||
public function getLastFlushTime();
|
||||
|
||||
/**
|
||||
* Get a single entry
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return stdClass
|
||||
* Cache entry or false if the entry does not exists.
|
||||
*/
|
||||
public function get($id);
|
||||
|
||||
/**
|
||||
* Get multiple entries
|
||||
*
|
||||
* @param string[] $idList
|
||||
*
|
||||
* @return stdClass[]
|
||||
* Existing cache entries keyed by id,
|
||||
*/
|
||||
public function getMultiple(array $idList);
|
||||
|
||||
/**
|
||||
* Set a single entry
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $data
|
||||
* @param int $ttl
|
||||
* @param boolean $volatile
|
||||
*/
|
||||
public function set($id, $data, $ttl = null, $volatile = false);
|
||||
|
||||
/**
|
||||
* Delete a single entry
|
||||
*
|
||||
* @param string $cid
|
||||
*/
|
||||
public function delete($id);
|
||||
|
||||
/**
|
||||
* Delete multiple entries
|
||||
*
|
||||
* This method should not use a single DEL command but use a pipeline instead
|
||||
*
|
||||
* @param array $idList
|
||||
*/
|
||||
public function deleteMultiple(array $idList);
|
||||
|
||||
/**
|
||||
* Delete entries by prefix
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function deleteByPrefix($prefix);
|
||||
|
||||
/**
|
||||
* Flush all entries
|
||||
*/
|
||||
public function flush();
|
||||
|
||||
/**
|
||||
* Flush all entries marked as temporary
|
||||
*/
|
||||
public function flushVolatile();
|
||||
}
|
39
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php
Normal file
39
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* - Improve lua scripts by using SCAN family commands
|
||||
* - Deambiguate why we need the namespace only for flush*() operations
|
||||
* - Implement the isEmpty() method by using SCAN or KEYS
|
||||
*/
|
||||
abstract class Redis_Cache_Base extends Redis_AbstractBackend
|
||||
{
|
||||
/**
|
||||
* Lastest cache flush KEY name
|
||||
*/
|
||||
const LAST_FLUSH_KEY = '_last_flush';
|
||||
|
||||
/**
|
||||
* Delete by prefix lua script
|
||||
*/
|
||||
const EVAL_DELETE_PREFIX = <<<EOT
|
||||
local keys = redis.call("KEYS", ARGV[1])
|
||||
for i, k in ipairs(keys) do
|
||||
redis.call("DEL", k)
|
||||
end
|
||||
return 1
|
||||
EOT;
|
||||
|
||||
/**
|
||||
* Delete volatile by prefix lua script
|
||||
*/
|
||||
const EVAL_DELETE_VOLATILE = <<<EOT
|
||||
local keys = redis.call('KEYS', ARGV[1])
|
||||
for i, k in ipairs(keys) do
|
||||
if "1" == redis.call("HGET", k, "volatile") then
|
||||
redis.call("DEL", k)
|
||||
end
|
||||
end
|
||||
return 1
|
||||
EOT;
|
||||
}
|
149
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php
Normal file
149
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis cache backend.
|
||||
*/
|
||||
class Redis_Cache_PhpRedis extends Redis_Cache_Base
|
||||
{
|
||||
public function setLastFlushTimeFor($time, $volatile = false)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
|
||||
if ($volatile) {
|
||||
$client->hset($key, 'volatile', $time);
|
||||
} else {
|
||||
$client->hmset($key, array(
|
||||
'permanent' => $time,
|
||||
'volatile' => $time,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
$values = $client->hmget($key, array("permanent", "volatile"));
|
||||
|
||||
if (empty($values) || !is_array($values)) {
|
||||
$ret = array(0, 0);
|
||||
} else {
|
||||
if (empty($values['permanent'])) {
|
||||
$values['permanent'] = 0;
|
||||
}
|
||||
if (empty($values['volatile'])) {
|
||||
$values['volatile'] = 0;
|
||||
}
|
||||
$ret = array($values['permanent'], $values['volatile']);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function get($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
$values = $client->hgetall($key);
|
||||
|
||||
// Recent versions of PhpRedis will return the Redis instance
|
||||
// instead of an empty array when the HGETALL target key does
|
||||
// not exists. I see what you did there.
|
||||
if (empty($values) || !is_array($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function getMultiple(array $idList)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$ret = array();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
foreach ($idList as $id) {
|
||||
$pipe->hgetall($this->getKey($id));
|
||||
}
|
||||
$replies = $pipe->exec();
|
||||
|
||||
foreach (array_values($idList) as $line => $id) {
|
||||
if (!empty($replies[$line]) && is_array($replies[$line])) {
|
||||
$ret[$id] = $replies[$line];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set($id, $data, $ttl = null, $volatile = false)
|
||||
{
|
||||
// Ensure TTL consistency: if the caller gives us an expiry timestamp
|
||||
// in the past the key will expire now and will never be read.
|
||||
// Behavior between Predis and PhpRedis seems to change here: when
|
||||
// setting a negative expire time, PhpRedis seems to ignore the
|
||||
// command and leave the key permanent.
|
||||
if (null !== $ttl && $ttl <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['volatile'] = (int)$volatile;
|
||||
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
$pipe->hmset($key, $data);
|
||||
|
||||
if (null !== $ttl) {
|
||||
$pipe->expire($key, $ttl);
|
||||
}
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->getClient()->del($this->getKey($id));
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $idList)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
foreach ($idList as $id) {
|
||||
$pipe->del($this->getKey($id));
|
||||
}
|
||||
// Don't care if something failed.
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function deleteByPrefix($prefix)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey($prefix . '*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey('*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flushVolatile()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_VOLATILE, array($this->getKey('*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
145
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php
Normal file
145
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis cache backend.
|
||||
*/
|
||||
class Redis_Cache_Predis extends Redis_Cache_Base
|
||||
{
|
||||
public function setLastFlushTimeFor($time, $volatile = false)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
|
||||
if ($volatile) {
|
||||
$client->hset($key, 'volatile', $time);
|
||||
} else {
|
||||
$client->hmset($key, array(
|
||||
'permanent' => $time,
|
||||
'volatile' => $time,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
$values = $client->hmget($key, array("permanent", "volatile"));
|
||||
|
||||
if (empty($values) || !is_array($values)) {
|
||||
$values = array(0, 0);
|
||||
} else {
|
||||
if (empty($values[0])) {
|
||||
$values[0] = 0;
|
||||
}
|
||||
if (empty($values[1])) {
|
||||
$values[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function get($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
$values = $client->hgetall($key);
|
||||
|
||||
// Recent versions of PhpRedis will return the Redis instance
|
||||
// instead of an empty array when the HGETALL target key does
|
||||
// not exists. I see what you did there.
|
||||
if (empty($values) || !is_array($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function getMultiple(array $idList)
|
||||
{
|
||||
$ret = array();
|
||||
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
foreach ($idList as $id) {
|
||||
$pipe->hgetall($this->getKey($id));
|
||||
}
|
||||
$replies = $pipe->execute();
|
||||
|
||||
foreach (array_values($idList) as $line => $id) {
|
||||
// HGETALL signature seems to differ depending on Predis versions.
|
||||
// This was found just after Predis update. Even though I'm not sure
|
||||
// this comes from Predis or just because we're misusing it.
|
||||
if (!empty($replies[$line]) && is_array($replies[$line])) {
|
||||
$ret[$id] = $replies[$line];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set($id, $data, $ttl = null, $volatile = false)
|
||||
{
|
||||
// Ensure TTL consistency: if the caller gives us an expiry timestamp
|
||||
// in the past the key will expire now and will never be read.
|
||||
// Behavior between Predis and PhpRedis seems to change here: when
|
||||
// setting a negative expire time, PhpRedis seems to ignore the
|
||||
// command and leave the key permanent.
|
||||
if (null !== $ttl && $ttl <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $this->getKey($id);
|
||||
|
||||
$data['volatile'] = (int)$volatile;
|
||||
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
$pipe->hmset($key, $data);
|
||||
if (null !== $ttl) {
|
||||
$pipe->expire($key, $ttl);
|
||||
}
|
||||
$pipe->execute();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey($id));
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $idList)
|
||||
{
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
foreach ($idList as $id) {
|
||||
$pipe->del($this->getKey($id));
|
||||
}
|
||||
$pipe->execute();
|
||||
}
|
||||
|
||||
public function deleteByPrefix($prefix)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey($prefix . '*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey('*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flushVolatile()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_VOLATILE, 0, $this->getKey('*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This typically brings 80..85% compression in ~20ms/mb write, 5ms/mb read.
|
||||
*/
|
||||
class Redis_CacheCompressed extends Redis_Cache implements DrupalCacheInterface
|
||||
{
|
||||
private $compressionSizeThreshold = 100;
|
||||
private $compressionRatio = 1;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($bin)
|
||||
{
|
||||
parent::__construct($bin);
|
||||
|
||||
$this->compressionSizeThreshold = (int)variable_get('cache_compression_size_threshold', 100);
|
||||
if ($this->compressionSizeThreshold < 0) {
|
||||
trigger_error('cache_compression_size_threshold must be 0 or a positive integer, negative value found, switching back to default 100', E_USER_WARNING);
|
||||
$this->compressionSizeThreshold = 100;
|
||||
}
|
||||
|
||||
// Minimum compression level (1) has good ratio in low time.
|
||||
$this->compressionRatio = (int)variable_get('cache_compression_ratio', 1);
|
||||
if ($this->compressionRatio < 1 || 9 < $this->compressionRatio) {
|
||||
trigger_error('cache_compression_ratio must be between 1 and 9, out of bounds value found, switching back to default 1', E_USER_WARNING);
|
||||
$this->compressionRatio = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
$hash = parent::createEntryHash($cid, $data, $expire);
|
||||
|
||||
// Empiric level when compression makes sense.
|
||||
if (!$this->compressionSizeThreshold || strlen($hash['data']) > $this->compressionSizeThreshold) {
|
||||
|
||||
$hash['data'] = gzcompress($hash['data'], $this->compressionRatio);
|
||||
$hash['compressed'] = true;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function expandEntry(array $values, $flushPerm, $flushVolatile)
|
||||
{
|
||||
if (!empty($values['data']) && !empty($values['compressed'])) {
|
||||
// Uncompress, suppress warnings e.g. for broken CRC32.
|
||||
$values['data'] = @gzuncompress($values['data']);
|
||||
|
||||
// In such cases, void the cache entry.
|
||||
if ($values['data'] === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::expandEntry($values, $flushPerm, $flushVolatile);
|
||||
}
|
||||
}
|
241
sites/all/modules/contrib/dev/redis/lib/Redis/Client.php
Normal file
241
sites/all/modules/contrib/dev/redis/lib/Redis/Client.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
// It may happen we get here with no autoloader set during the Drupal core
|
||||
// early bootstrap phase, at cache backend init time.
|
||||
if (!interface_exists('Redis_Client_FactoryInterface')) {
|
||||
require_once dirname(__FILE__) . '/Client/FactoryInterface.php';
|
||||
require_once dirname(__FILE__) . '/Client/Manager.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* This static class only reason to exist is to tie Drupal global
|
||||
* configuration to OOP driven code of this module: it will handle
|
||||
* everything that must be read from global configuration and let
|
||||
* other components live without any existence of it
|
||||
*/
|
||||
class Redis_Client
|
||||
{
|
||||
/**
|
||||
* Cache implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_CACHE = 'Redis_Cache_';
|
||||
|
||||
/**
|
||||
* Lock implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_LOCK = 'Redis_Lock_';
|
||||
|
||||
/**
|
||||
* Cache implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_QUEUE = 'Redis_Queue_';
|
||||
|
||||
/**
|
||||
* Path implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_PATH = 'Redis_Path_';
|
||||
|
||||
/**
|
||||
* Client factory implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_CLIENT = 'Redis_Client_';
|
||||
|
||||
/**
|
||||
* @var Redis_Client_Manager
|
||||
*/
|
||||
private static $manager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
static protected $globalPrefix;
|
||||
|
||||
/**
|
||||
* Get site default global prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getGlobalPrefix()
|
||||
{
|
||||
// Provide a fallback for multisite. This is on purpose not inside the
|
||||
// getPrefixForBin() function in order to decouple the unified prefix
|
||||
// variable logic and custom module related security logic, that is not
|
||||
// necessary for all backends. We can't just use HTTP_HOST, as multiple
|
||||
// hosts might be using the same database. Or, more commonly, a site
|
||||
// might not be a multisite at all, but might be using Drush leading to
|
||||
// a separate HTTP_HOST of 'default'. Likewise, we can't rely on
|
||||
// conf_path(), as settings.php might be modifying what database to
|
||||
// connect to. To mirror what core does with database caching we use
|
||||
// the DB credentials to inform our cache key.
|
||||
if (null === self::$globalPrefix) {
|
||||
if (isset($GLOBALS['db_url']) && is_string($GLOBALS['db_url'])) {
|
||||
// Drupal 6 specifics when using the cache_backport module, we
|
||||
// therefore cannot use \Database class to determine database
|
||||
// settings.
|
||||
self::$globalPrefix = md5($GLOBALS['db_url']);
|
||||
} else {
|
||||
require_once DRUPAL_ROOT . '/includes/database/database.inc';
|
||||
$dbInfo = Database::getConnectionInfo();
|
||||
$active = $dbInfo['default'];
|
||||
self::$globalPrefix = md5($active['host'] . $active['database'] . $active['prefix']['default']);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$globalPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global default prefix
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getDefaultPrefix($namespace = null)
|
||||
{
|
||||
$ret = null;
|
||||
|
||||
if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
|
||||
$ret = $GLOBALS['drupal_test_info']['test_run_id'];
|
||||
} else {
|
||||
$prefixes = variable_get('cache_prefix', null);
|
||||
|
||||
if (is_string($prefixes)) {
|
||||
// Variable can be a string which then considered as a default
|
||||
// behavior.
|
||||
$ret = $prefixes;
|
||||
} else if (null !== $namespace && isset($prefixes[$namespace])) {
|
||||
if (false !== $prefixes[$namespace]) {
|
||||
// If entry is set and not false an explicit prefix is set
|
||||
// for the bin.
|
||||
$ret = $prefixes[$namespace];
|
||||
} else {
|
||||
// If we have an explicit false it means no prefix whatever
|
||||
// is the default configuration.
|
||||
$ret = '';
|
||||
}
|
||||
} else {
|
||||
// Key is not set, we can safely rely on default behavior.
|
||||
if (isset($prefixes['default']) && false !== $prefixes['default']) {
|
||||
$ret = $prefixes['default'];
|
||||
} else {
|
||||
// When default is not set or an explicit false this means
|
||||
// no prefix.
|
||||
$ret = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ret)) {
|
||||
$ret = Redis_Client::getGlobalPrefix();
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client manager
|
||||
*
|
||||
* @return Redis_Client_Manager
|
||||
*/
|
||||
static public function getManager()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (null === self::$manager) {
|
||||
|
||||
$className = self::getClass(self::REDIS_IMPL_CLIENT);
|
||||
$factory = new $className();
|
||||
|
||||
// Build server list from conf
|
||||
$serverList = array();
|
||||
if (isset($conf['redis_servers'])) {
|
||||
$serverList = $conf['redis_servers'];
|
||||
}
|
||||
|
||||
if (empty($serverList) || !isset($serverList['default'])) {
|
||||
|
||||
// Backward configuration compatibility with older versions
|
||||
$serverList[Redis_Client_Manager::REALM_DEFAULT] = array();
|
||||
|
||||
foreach (array('host', 'port', 'base', 'password', 'socket') as $key) {
|
||||
if (isset($conf['redis_client_' . $key])) {
|
||||
$serverList[Redis_Client_Manager::REALM_DEFAULT][$key] = $conf['redis_client_' . $key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$manager = new Redis_Client_Manager($factory, $serverList);
|
||||
}
|
||||
|
||||
return self::$manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find client class name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getClientInterfaceName()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!empty($conf['redis_client_interface'])) {
|
||||
return $conf['redis_client_interface'];
|
||||
} else if (class_exists('Predis\Client')) {
|
||||
// Transparent and abitrary preference for Predis library.
|
||||
return $conf['redis_client_interface'] = 'Predis';
|
||||
} else if (class_exists('Redis')) {
|
||||
// Fallback on PhpRedis if available.
|
||||
return $conf['redis_client_interface'] = 'PhpRedis';
|
||||
} else {
|
||||
throw new Exception("No client interface set.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit test use only
|
||||
*/
|
||||
static public function reset(Redis_Client_Manager $manager = null)
|
||||
{
|
||||
self::$manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client for the 'default' realm
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getClient()
|
||||
{
|
||||
return self::getManager()->getClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific class implementing the current client usage for the specific
|
||||
* asked core subsystem.
|
||||
*
|
||||
* @param string $system
|
||||
* One of the Redis_Client::IMPL_* constant.
|
||||
* @param string $clientName
|
||||
* Client name, if fixed.
|
||||
*
|
||||
* @return string
|
||||
* Class name, if found.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static public function getClass($system)
|
||||
{
|
||||
$class = $system . self::getClientInterfaceName();
|
||||
|
||||
if (!class_exists($class)) {
|
||||
throw new Exception(sprintf("Class '%s' does not exist", $class));
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client proxy, client handling class tied to the bare mininum.
|
||||
*/
|
||||
interface Redis_Client_FactoryInterface {
|
||||
/**
|
||||
* Get the connected client instance.
|
||||
*
|
||||
* @param array $options
|
||||
* Options from the server pool configuration that may contain:
|
||||
* - host
|
||||
* - port
|
||||
* - database
|
||||
* - password
|
||||
* - socket
|
||||
*
|
||||
* @return mixed
|
||||
* Real client depends from the library behind.
|
||||
*/
|
||||
public function getClient($options = array());
|
||||
|
||||
/**
|
||||
* Get underlaying library name used.
|
||||
*
|
||||
* This can be useful for contribution code that may work with only some of
|
||||
* the provided clients.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
144
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php
Normal file
144
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client pool manager for multi-server configurations
|
||||
*/
|
||||
class Redis_Client_Manager
|
||||
{
|
||||
/**
|
||||
* Redis default host
|
||||
*/
|
||||
const REDIS_DEFAULT_HOST = '127.0.0.1';
|
||||
|
||||
/**
|
||||
* Redis default port
|
||||
*/
|
||||
const REDIS_DEFAULT_PORT = 6379;
|
||||
|
||||
/**
|
||||
* Redis default socket (will override host and port)
|
||||
*/
|
||||
const REDIS_DEFAULT_SOCKET = null;
|
||||
|
||||
/**
|
||||
* Redis default database: will select none (Database 0)
|
||||
*/
|
||||
const REDIS_DEFAULT_BASE = null;
|
||||
|
||||
/**
|
||||
* Redis default password: will not authenticate
|
||||
*/
|
||||
const REDIS_DEFAULT_PASSWORD = null;
|
||||
|
||||
/**
|
||||
* Default realm
|
||||
*/
|
||||
const REALM_DEFAULT = 'default';
|
||||
|
||||
/**
|
||||
* Client interface name (PhpRedis or Predis)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $interfaceName;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private $serverList = array();
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $clients = array();
|
||||
|
||||
/**
|
||||
* @var Redis_Client_FactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param Redis_Client_FactoryInterface $factory
|
||||
* Client factory
|
||||
* @param array $serverList
|
||||
* Server connection info list
|
||||
*/
|
||||
public function __construct(Redis_Client_FactoryInterface $factory, $serverList = array())
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->serverList = $serverList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client for the given realm
|
||||
*
|
||||
* @param string $realm
|
||||
* @param boolean $allowDefault
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getClient($realm = self::REALM_DEFAULT, $allowDefault = true)
|
||||
{
|
||||
if (!isset($this->clients[$realm])) {
|
||||
$client = $this->createClient($realm);
|
||||
|
||||
if (false === $client) {
|
||||
if (self::REALM_DEFAULT !== $realm && $allowDefault) {
|
||||
$this->clients[$realm] = $this->getClient(self::REALM_DEFAULT);
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf("Could not find client for realm '%s'", $realm));
|
||||
}
|
||||
} else {
|
||||
$this->clients[$realm] = $client;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->clients[$realm];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build connection parameters array from current Drupal settings
|
||||
*
|
||||
* @param string $realm
|
||||
*
|
||||
* @return boolean|string[]
|
||||
* A key-value pairs of configuration values or false if realm is
|
||||
* not defined per-configuration
|
||||
*/
|
||||
private function buildOptions($realm)
|
||||
{
|
||||
$info = null;
|
||||
|
||||
if (isset($this->serverList[$realm])) {
|
||||
$info = $this->serverList[$realm];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info += array(
|
||||
'host' => self::REDIS_DEFAULT_HOST,
|
||||
'port' => self::REDIS_DEFAULT_PORT,
|
||||
'base' => self::REDIS_DEFAULT_BASE,
|
||||
'password' => self::REDIS_DEFAULT_PASSWORD,
|
||||
'socket' => self::REDIS_DEFAULT_SOCKET
|
||||
);
|
||||
|
||||
return array_filter($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client singleton
|
||||
*/
|
||||
private function createClient($realm)
|
||||
{
|
||||
$info = $this->buildOptions($realm);
|
||||
|
||||
if (false === $info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->factory->getClient($info);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis client specific implementation.
|
||||
*/
|
||||
class Redis_Client_PhpRedis implements Redis_Client_FactoryInterface {
|
||||
|
||||
public function getClient($options = array()) {
|
||||
$client = new Redis;
|
||||
|
||||
if (!empty($options['socket'])) {
|
||||
$client->connect($options['socket']);
|
||||
} else {
|
||||
$client->connect($options['host'], $options['port']);
|
||||
}
|
||||
|
||||
if (isset($options['password'])) {
|
||||
$client->auth($options['password']);
|
||||
}
|
||||
|
||||
if (isset($options['base'])) {
|
||||
$client->select($options['base']);
|
||||
}
|
||||
|
||||
// Do not allow PhpRedis serialize itself data, we are going to do it
|
||||
// ourself. This will ensure less memory footprint on Redis size when
|
||||
// we will attempt to store small values.
|
||||
$client->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
145
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php
Normal file
145
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis client specific implementation.
|
||||
*/
|
||||
class Redis_Client_Predis implements Redis_Client_FactoryInterface {
|
||||
|
||||
/**
|
||||
* Circular depedency breaker.
|
||||
*/
|
||||
static protected $autoloaderRegistered = false;
|
||||
|
||||
/**
|
||||
* If the first cache get operation happens after the core autoloader has
|
||||
* been registered to PHP, during our autoloader registration we will
|
||||
* trigger it when calling class_exists(): core autoloader will then run
|
||||
* cache_get() during autoloading but sadly this will run our autoloader
|
||||
* registration once again. The second time we are called the circular
|
||||
* dependency breaker will act and we will do nothing, ending up in a
|
||||
* class instanciation attempt while the autoloader is still not loaded.
|
||||
*/
|
||||
static protected $stupidCoreWorkaround = 0;
|
||||
|
||||
/**
|
||||
* Define Predis base path if not already set, and if we need to set the
|
||||
* autoloader by ourself. This will ensure no crash. Best way would have
|
||||
* been that Drupal ships a PSR-0 autoloader, in which we could manually
|
||||
* add our library path.
|
||||
*
|
||||
* We cannot do that in the file header, PHP class_exists() function wont
|
||||
* see classes being loaded during the autoloading because this file is
|
||||
* loaded by another autoloader: attempting the class_exists() during a
|
||||
* pending autoloading would cause PHP to crash and ignore the rest of the
|
||||
* file silentely (WTF!?). By delaying this at the getClient() call we
|
||||
* ensure we are not in the class loading process anymore.
|
||||
*/
|
||||
public static function setPredisAutoload() {
|
||||
|
||||
if (self::$autoloaderRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$stupidCoreWorkaround++;
|
||||
|
||||
// If you attempt to set Drupal's bin cache_bootstrap using Redis, you
|
||||
// will experience an infinite loop (breaking by itself the second time
|
||||
// it passes by): the following call will wake up autoloaders (and we
|
||||
// want that to work since user may have set its own autoloader) but
|
||||
// will wake up Drupal's one too, and because Drupal core caches its
|
||||
// file map, this will trigger this method to be called a second time
|
||||
// and boom! Adios bye bye. That's why this will be called early in the
|
||||
// 'redis.autoload.inc' file instead.
|
||||
if (1 < self::$stupidCoreWorkaround || !class_exists('Predis\Client')) {
|
||||
|
||||
if (!defined('PREDIS_BASE_PATH')) {
|
||||
$search = DRUPAL_ROOT . '/sites/all/libraries/predis';
|
||||
define('PREDIS_BASE_PATH', $search);
|
||||
} else {
|
||||
$search = PREDIS_BASE_PATH;
|
||||
}
|
||||
|
||||
if (is_dir($search . '/src')) { // Predis v1.x
|
||||
define('PREDIS_VERSION_MAJOR', 1);
|
||||
} else if (is_dir($search . '/lib')) { // Predis v0.x
|
||||
define('PREDIS_VERSION_MAJOR', 0);
|
||||
} else {
|
||||
throw new Exception("PREDIS_BASE_PATH constant must be set, Predis library must live in sites/all/libraries/predis.");
|
||||
}
|
||||
|
||||
// Register a simple autoloader for Predis library. Since the Predis
|
||||
// library is PHP 5.3 only, we can afford doing closures safely.
|
||||
switch (PREDIS_VERSION_MAJOR) {
|
||||
|
||||
case 0:
|
||||
$autoload = function($classname) { // PSR-0 autoloader.
|
||||
if (0 === strpos($classname, 'Predis\\')) {
|
||||
$filename = PREDIS_BASE_PATH . '/lib/' . str_replace('\\', '/', $classname) . '.php';
|
||||
return (bool)require_once $filename;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Register a simple autoloader for Predis library. Since the Predis
|
||||
// library is PHP 5.3 only, we can afford doing closures safely.
|
||||
$autoload = function($classname) { // PSR-4 autoloader
|
||||
if (0 === strpos($classname, 'Predis\\')) {
|
||||
$filename = PREDIS_BASE_PATH . '/src/' . str_replace('\\', '/', substr($classname, 7)) . '.php';
|
||||
return (bool)require_once $filename;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if ($autoload) {
|
||||
spl_autoload_register($autoload);
|
||||
}
|
||||
|
||||
// Same reason why we have the stupid core workaround, if this happens
|
||||
// during a second autoload call, PHP won't call the newly registered
|
||||
// autoloader function, so just load the file.
|
||||
if (1 < self::$stupidCoreWorkaround) {
|
||||
call_user_func($autoload, 'Predis\Client');
|
||||
}
|
||||
}
|
||||
|
||||
self::$autoloaderRegistered = true;
|
||||
}
|
||||
|
||||
public function getClient($options = array()) {
|
||||
|
||||
self::setPredisAutoload();
|
||||
|
||||
if (!empty($options['socket'])) {
|
||||
$options['scheme'] = 'unix';
|
||||
$options['path'] = $options['socket'];
|
||||
}
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (!isset($value)) {
|
||||
unset($options[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// I'm not sure why but the error handler is driven crazy if timezone
|
||||
// is not set at this point.
|
||||
// Hopefully Drupal will restore the right one this once the current
|
||||
// account has logged in.
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
$client = new \Predis\Client($options);
|
||||
|
||||
if (isset($options['base']) && 0 !== $options['base']) {
|
||||
$client->select((int)$options['base']);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
31
sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php
Normal file
31
sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend singleton handling.
|
||||
*/
|
||||
class Redis_Lock {
|
||||
/**
|
||||
* @var Redis_Lock_BackendInterface
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Get actual lock backend.
|
||||
*
|
||||
* @return Redis_Lock_BackendInterface
|
||||
*/
|
||||
public static function getBackend()
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK);
|
||||
|
||||
self::$instance = new $className(
|
||||
Redis_Client::getClient(),
|
||||
Redis_Client::getDefaultPrefix('lock')
|
||||
);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend interface.
|
||||
*/
|
||||
interface Redis_Lock_BackendInterface {
|
||||
/**
|
||||
* Acquire lock.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name.
|
||||
* @param float $timeout = 30.0
|
||||
* (optional) Lock lifetime in seconds.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lockAcquire($name, $timeout = 30.0);
|
||||
|
||||
/**
|
||||
* Check if lock is available for acquire.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock to acquire.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lockMayBeAvailable($name);
|
||||
|
||||
/**
|
||||
* Wait a short amount of time before a second lock acquire attempt.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name currently being locked.
|
||||
* @param int $delay = 30
|
||||
* Miliseconds to wait for.
|
||||
*/
|
||||
public function lockWait($name, $delay = 30);
|
||||
|
||||
/**
|
||||
* Release given lock.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function lockRelease($name);
|
||||
|
||||
/**
|
||||
* Release all locks for the given lock token identifier.
|
||||
*
|
||||
* @param string $lockId = NULL
|
||||
* (optional) If none given, remove all lock from the current page.
|
||||
*/
|
||||
public function lockReleaseAll($lock_id = NULL);
|
||||
|
||||
/**
|
||||
* Get the unique page token for locks. Locks will be wipeout at each end of
|
||||
* page request on a token basis.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLockId();
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend shared methods.
|
||||
*/
|
||||
abstract class Redis_Lock_DefaultBackend
|
||||
extends Redis_AbstractBackend
|
||||
implements Redis_Lock_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Current page lock token identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_lockId;
|
||||
|
||||
/**
|
||||
* Existing locks for this page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_locks = array();
|
||||
|
||||
/**
|
||||
* Default implementation from actual Drupal core.
|
||||
*
|
||||
* @see Redis_Lock_BackendInterface::lockWait()
|
||||
*/
|
||||
public function lockWait($name, $delay = 30) {
|
||||
// Pause the process for short periods between calling
|
||||
// lock_may_be_available(). This prevents hitting the database with constant
|
||||
// database queries while waiting, which could lead to performance issues.
|
||||
// However, if the wait period is too long, there is the potential for a
|
||||
// large number of processes to be blocked waiting for a lock, especially
|
||||
// if the item being rebuilt is commonly requested. To address both of these
|
||||
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
|
||||
// time until it reaches 500ms. After this point polling will continue every
|
||||
// 500ms until $delay is reached.
|
||||
|
||||
// $delay is passed in seconds, but we will be using usleep(), which takes
|
||||
// microseconds as a parameter. Multiply it by 1 million so that all
|
||||
// further numbers are equivalent.
|
||||
$delay = (int) $delay * 1000000;
|
||||
|
||||
// Begin sleeping at 25ms.
|
||||
$sleep = 25000;
|
||||
while ($delay > 0) {
|
||||
// This function should only be called by a request that failed to get a
|
||||
// lock, so we sleep first to give the parallel request a chance to finish
|
||||
// and release the lock.
|
||||
usleep($sleep);
|
||||
// After each sleep, increase the value of $sleep until it reaches
|
||||
// 500ms, to reduce the potential for a lock stampede.
|
||||
$delay = $delay - $sleep;
|
||||
$sleep = min(500000, $sleep + 25000, $delay);
|
||||
if ($this->lockMayBeAvailable($name)) {
|
||||
// No longer need to wait.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// The caller must still wait longer to get the lock.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation from actual Drupal core.
|
||||
*
|
||||
* @see Redis_Lock_BackendInterface::getLockId()
|
||||
*/
|
||||
public function getLockId() {
|
||||
if (!isset($this->_lockId)) {
|
||||
$this->_lockId = uniqid(mt_rand(), TRUE);
|
||||
// We only register a shutdown function if a lock is used.
|
||||
drupal_register_shutdown_function('lock_release_all', $this->_lockId);
|
||||
}
|
||||
return $this->_lockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a redis key name for the current lock name
|
||||
*/
|
||||
public function getKey($name = null) {
|
||||
if (null === $name) {
|
||||
return parent::getKey('lock');
|
||||
} else {
|
||||
return parent::getKey(array('lock', $name));
|
||||
}
|
||||
}
|
||||
}
|
138
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php
Normal file
138
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis lock backend implementation.
|
||||
*
|
||||
* This implementation works with a single key per lock so is viable when
|
||||
* doing client side sharding and/or using consistent hashing algorithm.
|
||||
*/
|
||||
class Redis_Lock_PhpRedis extends Redis_Lock_DefaultBackend {
|
||||
|
||||
public function lockAcquire($name, $timeout = 30.0) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
// Insure that the timeout is at least 1 second, we cannot do otherwise with
|
||||
// Redis, this is a minor change to the function signature, but in real life
|
||||
// nobody will notice with so short duration.
|
||||
$timeout = ceil(max($timeout, 1));
|
||||
|
||||
// If we already have the lock, check for his owner and attempt a new EXPIRE
|
||||
// command on it.
|
||||
if (isset($this->_locks[$name])) {
|
||||
|
||||
// Create a new transaction, for atomicity.
|
||||
$client->watch($key);
|
||||
|
||||
// Global tells us we are the owner, but in real life it could have expired
|
||||
// and another process could have taken it, check that.
|
||||
if ($client->get($key) != $id) {
|
||||
// Explicit UNWATCH we are not going to run the MULTI/EXEC block.
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// See https://github.com/phpredis/phpredis#watch-unwatch
|
||||
// MULTI and other commands can fail, so we can't chain calls.
|
||||
if (FALSE !== ($result = $client->multi())) {
|
||||
$client->setex($key, $timeout, $id);
|
||||
$result = $client->exec();
|
||||
}
|
||||
|
||||
// Did it broke?
|
||||
if (FALSE === $result) {
|
||||
unset($this->_locks[$name]);
|
||||
// Explicit transaction release which also frees the WATCH'ed key.
|
||||
$client->discard();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
else {
|
||||
$client->watch($key);
|
||||
$owner = $client->get($key);
|
||||
|
||||
// If the $key is set they lock is not available
|
||||
if (!empty($owner) && $id != $owner) {
|
||||
$client->unwatch();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// See https://github.com/phpredis/phpredis#watch-unwatch
|
||||
// MULTI and other commands can fail, so we can't chain calls.
|
||||
if (FALSE !== ($result = $client->multi())) {
|
||||
$client->setex($key, $timeout, $id);
|
||||
$result->exec();
|
||||
}
|
||||
|
||||
// If another client modified the $key value, transaction will be discarded
|
||||
// $result will be set to FALSE. This means atomicity have been broken and
|
||||
// the other client took the lock instead of us.
|
||||
if (FALSE === $result) {
|
||||
// Explicit transaction release which also frees the WATCH'ed key.
|
||||
$client->discard();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Register the lock.
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function lockMayBeAvailable($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
$value = $client->get($key);
|
||||
|
||||
return FALSE === $value || $id == $value;
|
||||
}
|
||||
|
||||
public function lockRelease($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
unset($this->_locks[$name]);
|
||||
|
||||
// Ensure the lock deletion is an atomic transaction. If another thread
|
||||
// manages to removes all lock, we can not alter it anymore else we will
|
||||
// release the lock for the other thread and cause race conditions.
|
||||
$client->watch($key);
|
||||
|
||||
if ($client->get($key) == $id) {
|
||||
$client->multi();
|
||||
$client->delete($key);
|
||||
$client->exec();
|
||||
}
|
||||
else {
|
||||
$client->unwatch();
|
||||
}
|
||||
}
|
||||
|
||||
public function lockReleaseAll($lock_id = NULL) {
|
||||
if (!isset($lock_id) && empty($this->_locks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $this->getClient();
|
||||
$id = isset($lock_id) ? $lock_id : $this->getLockId();
|
||||
|
||||
// We can afford to deal with a slow algorithm here, this should not happen
|
||||
// on normal run because we should have removed manually all our locks.
|
||||
foreach (array_keys($this->_locks) as $name) {
|
||||
$key = $this->getKey($name);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (empty($owner) || $owner == $id) {
|
||||
$client->delete($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
137
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php
Normal file
137
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis lock backend implementation.
|
||||
*
|
||||
* This implementation works with a single key per lock so is viable when
|
||||
* doing client side sharding and/or using consistent hashing algorithm.
|
||||
*/
|
||||
class Redis_Lock_Predis extends Redis_Lock_DefaultBackend {
|
||||
|
||||
public function lockAcquire($name, $timeout = 30.0) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
// Insure that the timeout is at least 1 second, we cannot do otherwise with
|
||||
// Redis, this is a minor change to the function signature, but in real life
|
||||
// nobody will notice with so short duration.
|
||||
$timeout = ceil(max($timeout, 1));
|
||||
|
||||
// If we already have the lock, check for his owner and attempt a new EXPIRE
|
||||
// command on it.
|
||||
if (isset($this->_locks[$name])) {
|
||||
|
||||
// Create a new transaction, for atomicity.
|
||||
$client->watch($key);
|
||||
|
||||
// Global tells us we are the owner, but in real life it could have expired
|
||||
// and another process could have taken it, check that.
|
||||
if ($client->get($key) != $id) {
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
|
||||
$pipe->multi();
|
||||
$pipe->setex($key, $timeout, $id);
|
||||
$pipe->exec();
|
||||
});
|
||||
|
||||
$execReply = array_pop($replies);
|
||||
|
||||
if (FALSE === $execReply[0]) {
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$client->watch($key);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (!empty($owner) && $owner != $id) {
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
|
||||
$pipe->multi();
|
||||
$pipe->setex($key, $timeout, $id);
|
||||
$pipe->exec();
|
||||
});
|
||||
|
||||
$execReply = array_pop($replies);
|
||||
|
||||
// If another client modified the $key value, transaction will be discarded
|
||||
// $result will be set to FALSE. This means atomicity have been broken and
|
||||
// the other client took the lock instead of us.
|
||||
// EXPIRE and SETEX won't return something here, EXEC return is index 0
|
||||
// This was determined debugging, seems to be Predis specific.
|
||||
if (FALSE === $execReply[0]) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Register the lock and return.
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function lockMayBeAvailable($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
$value = $client->get($key);
|
||||
|
||||
return empty($value) || $id == $value;
|
||||
}
|
||||
|
||||
public function lockRelease($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
unset($this->_locks[$name]);
|
||||
|
||||
// Ensure the lock deletion is an atomic transaction. If another thread
|
||||
// manages to removes all lock, we can not alter it anymore else we will
|
||||
// release the lock for the other thread and cause race conditions.
|
||||
$client->watch($key);
|
||||
|
||||
if ($client->get($key) == $id) {
|
||||
$client->multi();
|
||||
$client->del(array($key));
|
||||
$client->exec();
|
||||
}
|
||||
else {
|
||||
$client->unwatch();
|
||||
}
|
||||
}
|
||||
|
||||
public function lockReleaseAll($lock_id = NULL) {
|
||||
if (!isset($lock_id) && empty($this->_locks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $this->getClient();
|
||||
$id = isset($lock_id) ? $lock_id : $this->getLockId();
|
||||
|
||||
// We can afford to deal with a slow algorithm here, this should not happen
|
||||
// on normal run because we should have removed manually all our locks.
|
||||
foreach (array_keys($this->_locks) as $name) {
|
||||
$key = $this->getKey($name);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (empty($owner) || $owner == $id) {
|
||||
$client->del(array($key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Common implementation for Redis-based implementations
|
||||
*/
|
||||
abstract class Redis_Path_AbstractHashLookup extends Redis_AbstractBackend implements
|
||||
Redis_Path_HashLookupInterface
|
||||
{
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $hkey
|
||||
* @param string $hvalue
|
||||
*/
|
||||
abstract protected function saveInHash($key, $hkey, $hvalue);
|
||||
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $hkey
|
||||
* @param string $hvalue
|
||||
*/
|
||||
abstract protected function deleteInHash($key, $hkey, $hvalue);
|
||||
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $keyPrefix
|
||||
* @param string $hkey
|
||||
* @param string $language
|
||||
*/
|
||||
abstract protected function lookupInHash($keyPrefix, $hkey, $language = null);
|
||||
|
||||
/**
|
||||
* Normalize value to avoid duplicate or false negatives
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function normalize($value)
|
||||
{
|
||||
if (null !== $value) {
|
||||
return strtolower(trim($value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveAlias($source, $alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
$source = $this->normalize($source);
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
}
|
||||
|
||||
if (!empty($source)) {
|
||||
$this->saveInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias);
|
||||
}
|
||||
if (!empty($alias)) {
|
||||
$this->saveInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAlias($source, $alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
$source = $this->normalize($source);
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
}
|
||||
|
||||
$this->deleteInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias);
|
||||
$this->deleteInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupAlias($source, $language = null)
|
||||
{
|
||||
$source = $this->normalize($source);
|
||||
|
||||
return $this->lookupInHash(self::KEY_ALIAS, $source, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupSource($alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
|
||||
return $this->lookupInHash(self::KEY_SOURCE, $alias, $language);
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Very fast hash based lookup interface.
|
||||
*
|
||||
* This will work for any key-value store whether it's APC, Redis, memcache...
|
||||
* Rationale behind this is that Drupal calls hundreds of time per request the
|
||||
* drupal_lookup_path() function and we need it to be very fast. The key of
|
||||
* success to keep it stupid simple and coherent as the same time is that we
|
||||
* consider this backend as a cache (more or less permanent) that might be
|
||||
* cleared at any time, and synchronized as when necessary or incrementally.
|
||||
* This should be very fast.
|
||||
*
|
||||
* Redis implementation will be the following:
|
||||
*
|
||||
* Aliases are stored into a Redis HASH and are stored per language basis.
|
||||
* Key is:
|
||||
* [SITEPREFIX:]path:dst:LANGUAGE
|
||||
* Keys inside the hash are a MD5() of the source and values are the alias
|
||||
*
|
||||
* Sources are also stored the same way except the HASH key is the following:
|
||||
* [SITEPREFIX:]path:src:LANGUAGE
|
||||
* Keys inside the hash are a MD5() of the alias and values are the sources.
|
||||
*
|
||||
* In both case values are a comma separated list of string values.
|
||||
*
|
||||
* The MD5() should give us low collision algorithm and we'll keep it until
|
||||
* no one experiences any problem.
|
||||
*
|
||||
* Alias and sources are always looked up using the language, hence the
|
||||
* different keys for different languages.
|
||||
*/
|
||||
interface Redis_Path_HashLookupInterface
|
||||
{
|
||||
/**
|
||||
* Alias HASH key prefix
|
||||
*/
|
||||
const KEY_ALIAS = 'path:a';
|
||||
|
||||
/**
|
||||
* Source HASH key prefix
|
||||
*/
|
||||
const KEY_SOURCE = 'path:s';
|
||||
|
||||
/**
|
||||
* Null value (not existing yet cached value)
|
||||
*/
|
||||
const VALUE_NULL = '!';
|
||||
|
||||
/**
|
||||
* Values separator for hash values
|
||||
*/
|
||||
const VALUE_SEPARATOR = '#';
|
||||
|
||||
/**
|
||||
* Alias is being inserted with the given source
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*/
|
||||
public function saveAlias($source, $alias, $language = null);
|
||||
|
||||
/**
|
||||
* Alias is being deleted for the given source
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*/
|
||||
public function deleteAlias($source, $alias, $language = null);
|
||||
|
||||
/**
|
||||
* A language is being deleted
|
||||
*
|
||||
* @param string $language
|
||||
*/
|
||||
public function deleteLanguage($language);
|
||||
|
||||
/**
|
||||
* Lookup any alias for the given source
|
||||
*
|
||||
* First that has been inserted wins over the others
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $language
|
||||
*
|
||||
* @return string|null|false
|
||||
* - The string value if found
|
||||
* - null if not found
|
||||
* - false if set as non existing
|
||||
*/
|
||||
public function lookupAlias($source, $language = null);
|
||||
|
||||
/**
|
||||
* Lookup any source for the given alias
|
||||
*
|
||||
* First that has been inserted wins over the others
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*
|
||||
* @return string|null|false
|
||||
* - The string value if found
|
||||
* - null if not found
|
||||
* - false if set as non existing
|
||||
*/
|
||||
public function lookupSource($alias, $language = null);
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Null implementation.
|
||||
*/
|
||||
class Redis_Path_NullHashLookup implements Redis_Path_HashLookupInterface
|
||||
{
|
||||
public function saveAlias($source, $alias, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteAlias($source, $alias, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
}
|
||||
|
||||
public function lookupAlias($source, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function lookupSource($alias, $language = null)
|
||||
{
|
||||
}
|
||||
}
|
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php
Normal file
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis implementation.
|
||||
*
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Path_PhpRedis extends Redis_Path_AbstractHashLookup
|
||||
{
|
||||
protected function saveInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value === self::VALUE_NULL) { // Remove any null values
|
||||
$value = null;
|
||||
}
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (!in_array($hvalue, $existing)) {
|
||||
// Prepend the most recent path to ensure it always be
|
||||
// first fetched one
|
||||
// @todo Ensure in case of update that its position does
|
||||
// not changes (pid ordering in Drupal core)
|
||||
$value = $hvalue . self::VALUE_SEPARATOR . $value;
|
||||
} else { // Do nothing on empty value
|
||||
$value = null;
|
||||
}
|
||||
} else if (empty($hvalue)) {
|
||||
$value = self::VALUE_NULL;
|
||||
} else {
|
||||
$value = $hvalue;
|
||||
}
|
||||
|
||||
if (!empty($value)) {
|
||||
$client->hset($key, $hkey, $value);
|
||||
}
|
||||
// Empty value here means that we already got it
|
||||
}
|
||||
|
||||
protected function deleteInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (false !== ($index = array_search($hvalue, $existing))) {
|
||||
if (1 === count($existing)) {
|
||||
$client->hdel($key, $hkey);
|
||||
} else {
|
||||
unset($existing[$index]);
|
||||
$client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function lookupInHash($keyPrefix, $hkey, $language = null)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
$doNoneLookup = false;
|
||||
} else if (LANGUAGE_NONE === $language) {
|
||||
$doNoneLookup = false;
|
||||
} else {
|
||||
$doNoneLookup = true;
|
||||
}
|
||||
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey);
|
||||
if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) {
|
||||
$previous = $ret;
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey);
|
||||
if (!$ret || self::VALUE_NULL === $ret) {
|
||||
// Restore null placeholder else we loose conversion to false
|
||||
// and drupal_lookup_path() would attempt saving it once again
|
||||
$ret = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::VALUE_NULL === $ret) {
|
||||
return false; // Needs conversion
|
||||
}
|
||||
if (empty($ret)) {
|
||||
return null; // Value not found
|
||||
}
|
||||
|
||||
$existing = explode(self::VALUE_SEPARATOR, $ret);
|
||||
|
||||
return reset($existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey(array(self::KEY_ALIAS, $language)));
|
||||
$client->del($this->getKey(array(self::KEY_SOURCE, $language)));
|
||||
}
|
||||
}
|
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php
Normal file
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis implementation.
|
||||
*
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Path_Predis extends Redis_Path_AbstractHashLookup
|
||||
{
|
||||
protected function saveInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value === self::VALUE_NULL) { // Remove any null values
|
||||
$value = null;
|
||||
}
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (!in_array($hvalue, $existing)) {
|
||||
// Prepend the most recent path to ensure it always be
|
||||
// first fetched one
|
||||
// @todo Ensure in case of update that its position does
|
||||
// not changes (pid ordering in Drupal core)
|
||||
$value = $hvalue . self::VALUE_SEPARATOR . $value;
|
||||
} else { // Do nothing on empty value
|
||||
$value = null;
|
||||
}
|
||||
} else if (empty($hvalue)) {
|
||||
$value = self::VALUE_NULL;
|
||||
} else {
|
||||
$value = $hvalue;
|
||||
}
|
||||
|
||||
if (!empty($value)) {
|
||||
$client->hset($key, $hkey, $value);
|
||||
}
|
||||
// Empty value here means that we already got it
|
||||
}
|
||||
|
||||
protected function deleteInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (false !== ($index = array_search($hvalue, $existing))) {
|
||||
if (1 === count($existing)) {
|
||||
$client->hdel($key, $hkey);
|
||||
} else {
|
||||
unset($existing[$index]);
|
||||
$client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function lookupInHash($keyPrefix, $hkey, $language = null)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
$doNoneLookup = false;
|
||||
} else if (LANGUAGE_NONE === $language) {
|
||||
$doNoneLookup = false;
|
||||
} else {
|
||||
$doNoneLookup = true;
|
||||
}
|
||||
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey);
|
||||
if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) {
|
||||
$previous = $ret;
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey);
|
||||
if (!$ret || self::VALUE_NULL === $ret) {
|
||||
// Restore null placeholder else we loose conversion to false
|
||||
// and drupal_lookup_path() would attempt saving it once again
|
||||
$ret = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::VALUE_NULL === $ret) {
|
||||
return false; // Needs conversion
|
||||
}
|
||||
if (empty($ret)) {
|
||||
return null; // Value not found
|
||||
}
|
||||
|
||||
$existing = explode(self::VALUE_SEPARATOR, $ret);
|
||||
|
||||
return reset($existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey(array(self::KEY_ALIAS, $language)));
|
||||
$client->del($this->getKey(array(self::KEY_SOURCE, $language)));
|
||||
}
|
||||
}
|
58
sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php
Normal file
58
sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
class Redis_Queue implements DrupalReliableQueueInterface
|
||||
{
|
||||
/**
|
||||
* @var DrupalQueueInterface
|
||||
*/
|
||||
protected $backend;
|
||||
|
||||
/**
|
||||
* Default contructor
|
||||
*
|
||||
* Beware that DrupalQueueInterface does not defines the __construct
|
||||
* method in the interface yet is being used from DrupalQueue::get()
|
||||
*
|
||||
* @param unknown $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_QUEUE);
|
||||
$this->backend = new $className(Redis_Client::getClient(), $name);
|
||||
}
|
||||
|
||||
public function createItem($data)
|
||||
{
|
||||
return $this->backend->createItem($data);
|
||||
}
|
||||
|
||||
public function numberOfItems()
|
||||
{
|
||||
return $this->backend->numberOfItems();
|
||||
}
|
||||
|
||||
public function claimItem($lease_time = 3600)
|
||||
{
|
||||
return $this->backend->claimItem($lease_time);
|
||||
}
|
||||
|
||||
public function deleteItem($item)
|
||||
{
|
||||
return $this->backend->deleteItem($item);
|
||||
}
|
||||
|
||||
public function releaseItem($item)
|
||||
{
|
||||
return $this->backend->releaseItem($item);
|
||||
}
|
||||
|
||||
public function createQueue()
|
||||
{
|
||||
return $this->backend->createQueue();
|
||||
}
|
||||
|
||||
public function deleteQueue()
|
||||
{
|
||||
return $this->backend->deleteQueue();
|
||||
}
|
||||
}
|
99
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php
Normal file
99
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Redis allows implementing reliable queues, here is the spec:
|
||||
*
|
||||
* - For each queue, you have 4 different HASH:
|
||||
*
|
||||
* - One for queued items queue:NAME:queued
|
||||
*
|
||||
* - One for claimed items being processed: queue:NAME:claimed
|
||||
*
|
||||
* - One for claimed items leave time: queue:NAME:leave
|
||||
* Items from this one will be arbitrarily fetched at cron
|
||||
* time and released when leave is outdated.
|
||||
*
|
||||
* - One containing the item values and other valuable stateful
|
||||
* information: queue:NAME:data ;
|
||||
*
|
||||
* - For example, current job maximum identifier (auto increment
|
||||
* emulation) will be stored in the "sequence" HASH key
|
||||
*
|
||||
* - All other keys within the HASH will be the items themselves,
|
||||
* keys for those will always be numeric
|
||||
*
|
||||
* - Each time a queue will be emptied, even during a pragmatic process,
|
||||
* it will be automatically deleted, reseting the sequence counter to
|
||||
* the 0 value each time
|
||||
*
|
||||
* - Algorithm is a variation of the one described in "Reliable queue"
|
||||
* section of http://redis.io/commands/rpoplpush and partial port of what
|
||||
* you can find in the http://drupal.org/project/redis_queue module.
|
||||
*
|
||||
* You will find the driver specific implementation in the Redis_Queue_*
|
||||
* classes as they may differ in how the API handles transaction, pipelining
|
||||
* and return values.
|
||||
*/
|
||||
abstract class Redis_Queue_Base extends Redis_AbstractBackend implements
|
||||
DrupalReliableQueueInterface
|
||||
{
|
||||
/**
|
||||
* Key prefix for queue data.
|
||||
*/
|
||||
const QUEUE_KEY_PREFIX = 'queue';
|
||||
|
||||
/**
|
||||
* Data HASH sequence key name.
|
||||
*/
|
||||
const QUEUE_HKEY_SEQ = 'seq';
|
||||
|
||||
/**
|
||||
* Get data HASH key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForData()
|
||||
{
|
||||
return $this->getKey('data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued items LIST key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForQueue()
|
||||
{
|
||||
return $this->getKey('queued');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get claimed LIST key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForClaimed()
|
||||
{
|
||||
return $this->getKey('claimed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Default contructor
|
||||
*
|
||||
* Beware that DrupalQueueInterface does not defines the __construct
|
||||
* method in the interface yet is being used from DrupalQueue::get()
|
||||
*
|
||||
* @param mixed $client
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($client, $name)
|
||||
{
|
||||
parent::__construct($client, self::QUEUE_KEY_PREFIX . $name);
|
||||
}
|
||||
}
|
106
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php
Normal file
106
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Queue_PhpRedis extends Redis_Queue_Base
|
||||
{
|
||||
public function createItem($data)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$dKey = $this->getKeyForData();
|
||||
$qKey = $this->getKeyForQueue();
|
||||
|
||||
// Identifier does not not need to be in the transaction,
|
||||
// in case of any error we'll just skip a value in the sequence.
|
||||
$id = $client->hincrby($dKey, self::QUEUE_HKEY_SEQ, 1);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->qid = $id;
|
||||
$record->data = $data;
|
||||
$record->timestamp = time();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
// Thanks to the redis_queue standalone module maintainers for
|
||||
// this piece of code, very effective. Note that we added the
|
||||
// pipeline thought.
|
||||
$pipe->hsetnx($dKey, $id, serialize($record));
|
||||
$pipe->llen($qKey);
|
||||
$pipe->lpush($qKey, $id);
|
||||
$ret = $pipe->exec();
|
||||
|
||||
if (!$success = ($ret[0] && $ret[1] < $ret[2])) {
|
||||
if ($ret[0]) {
|
||||
// HSETNEX worked but not the PUSH command we therefore
|
||||
// need to drop the inserted data. I would have prefered
|
||||
// a DISCARD instead but we are in pipelined transaction
|
||||
// we cannot actually do a DISCARD here.
|
||||
$client->hdel($dKey, $id);
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function numberOfItems()
|
||||
{
|
||||
return $this->getClient()->llen($this->getKeyForQueue());
|
||||
}
|
||||
|
||||
public function claimItem($lease_time = 30)
|
||||
{
|
||||
// @todo Deal with lease
|
||||
$client = $this->getClient();
|
||||
|
||||
$id = $client->rpoplpush(
|
||||
$this->getKeyForQueue(),
|
||||
$this->getKeyForClaimed()
|
||||
);
|
||||
|
||||
if ($id) {
|
||||
if ($item = $client->hget($this->getKeyForData(), $id)) {
|
||||
if ($item = unserialize($item)) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteItem($item)
|
||||
{
|
||||
$pipe = $this->getClient()->multi(Redis::PIPELINE);
|
||||
$pipe->lrem($this->getKeyForQueue(), $item->qid);
|
||||
$pipe->lrem($this->getKeyForClaimed(), $item->qid);
|
||||
$pipe->hdel($this->getKeyForData(), $item->qid);
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function releaseItem($item)
|
||||
{
|
||||
$pipe = $this->getClient()->multi(Redis::PIPELINE);
|
||||
$pipe->lrem($this->getKeyForClaimed(), $item->qid, -1);
|
||||
$pipe->lpush($this->getKeyForQueue(), $item->qid);
|
||||
$ret = $pipe->exec();
|
||||
|
||||
return $ret[0] && $ret[1];
|
||||
}
|
||||
|
||||
public function createQueue()
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteQueue()
|
||||
{
|
||||
$this->getClient()->del(
|
||||
$this->getKeyForQueue(),
|
||||
$this->getKeyForClaimed(),
|
||||
$this->getKeyForData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_AbstractUnitTestCase extends DrupalUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
static protected $loaderEnabled = false;
|
||||
|
||||
/**
|
||||
* Enable the autoloader
|
||||
*
|
||||
* This exists in this class in case the autoloader is not set into the
|
||||
* settings.php file or another way
|
||||
*
|
||||
* @return void|boolean
|
||||
*/
|
||||
static protected function enableAutoload()
|
||||
{
|
||||
if (self::$loaderEnabled) {
|
||||
return;
|
||||
}
|
||||
if (class_exists('Redis_Client')) {
|
||||
return;
|
||||
}
|
||||
|
||||
spl_autoload_register(function ($className) {
|
||||
$parts = explode('_', $className);
|
||||
if ('Redis' === $parts[0]) {
|
||||
$filename = __DIR__ . '/../lib/' . implode('/', $parts) . '.php';
|
||||
return (bool) include_once $filename;
|
||||
}
|
||||
return false;
|
||||
}, null, true);
|
||||
|
||||
self::$loaderEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal $conf array backup
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $originalConf = array(
|
||||
'cache_lifetime' => null,
|
||||
'cache_prefix' => null,
|
||||
'redis_client_interface' => null,
|
||||
'redis_eval_enabled' => null,
|
||||
'redis_flush_mode' => null,
|
||||
'redis_perm_ttl' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
* Prepare Drupal environmment for testing
|
||||
*/
|
||||
final private function prepareDrupalEnvironment()
|
||||
{
|
||||
// Site on which the tests are running may define this variable
|
||||
// in their own settings.php file case in which it will be merged
|
||||
// with testing site
|
||||
global $conf;
|
||||
foreach (array_keys($this->originalConf) as $key) {
|
||||
if (isset($conf[$key])) {
|
||||
$this->originalConf[$key] = $conf[$key];
|
||||
unset($conf[$key]);
|
||||
}
|
||||
}
|
||||
$conf['cache_prefix'] = $this->testId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore Drupal environment after testing.
|
||||
*/
|
||||
final private function restoreDrupalEnvironment()
|
||||
{
|
||||
$GLOBALS['conf'] = $this->originalConf + $GLOBALS['conf'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare client manager
|
||||
*/
|
||||
final private function prepareClientManager()
|
||||
{
|
||||
$interface = $this->getClientInterface();
|
||||
|
||||
if (null === $interface) {
|
||||
throw new \Exception("Test skipped due to missing driver");
|
||||
}
|
||||
|
||||
$GLOBALS['conf']['redis_client_interface'] = $interface;
|
||||
Redis_Client::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore client manager
|
||||
*/
|
||||
final private function restoreClientManager()
|
||||
{
|
||||
Redis_Client::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Redis configuration.
|
||||
*
|
||||
* Set up the needed variables using variable_set() if necessary.
|
||||
*
|
||||
* @return string
|
||||
* Client interface or null if not exists
|
||||
*/
|
||||
abstract protected function getClientInterface();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
self::enableAutoload();
|
||||
|
||||
$this->prepareDrupalEnvironment();
|
||||
$this->prepareClientManager();
|
||||
|
||||
parent::setUp();
|
||||
|
||||
drupal_install_schema('system');
|
||||
drupal_install_schema('locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tearDown()
|
||||
{
|
||||
drupal_uninstall_schema('locale');
|
||||
drupal_uninstall_schema('system');
|
||||
|
||||
$this->restoreDrupalEnvironment();
|
||||
$this->restoreClientManager();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Admin_VariableTestCase extends DrupalWebTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Redis variables',
|
||||
'description' => 'Checks that Redis module variables are correctly type hinted when saved.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected $adminUser;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp('redis');
|
||||
}
|
||||
|
||||
public function testSave()
|
||||
{
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer site configuration'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Tests port is an int.
|
||||
$this->drupalGet('admin/config/development/performance/redis');
|
||||
$edit = array(
|
||||
'redis_client_base' => '',
|
||||
'redis_client_port' => '1234',
|
||||
'redis_client_host' => 'localhost',
|
||||
'redis_client_interface' => '',
|
||||
);
|
||||
$this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration'));
|
||||
|
||||
// Force variable cache to refresh.
|
||||
$test = variable_initialize();
|
||||
$conf = &$GLOBALS['conf'];
|
||||
|
||||
$this->assertFalse(array_key_exists('redis_client_base', $conf), "Empty int value has been removed");
|
||||
$this->assertFalse(array_key_exists('redis_client_interface', $conf), "Empty string value has been removed");
|
||||
$this->assertIdentical($conf['redis_client_port'], 1234, "Saved int is an int");
|
||||
$this->assertIdentical($conf['redis_client_host'], 'localhost', "Saved string is a string");
|
||||
|
||||
$this->drupalGet('admin/config/development/performance/redis');
|
||||
$edit = array(
|
||||
'redis_client_base' => '0',
|
||||
'redis_client_port' => '1234',
|
||||
'redis_client_host' => 'localhost',
|
||||
'redis_client_interface' => '',
|
||||
);
|
||||
$this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration'));
|
||||
|
||||
// Force variable cache to refresh.
|
||||
$test = variable_initialize();
|
||||
$conf = &$GLOBALS['conf'];
|
||||
|
||||
$this->assertIdentical($conf['redis_client_base'], 0, "Saved 0 valueed int is an int");
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FixesUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FlushUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_AbstractUnitTestCase')) {
|
||||
require_once(__DIR__ . '/../AbstractUnitTestCase.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bugfixes made over time test class.
|
||||
*/
|
||||
abstract class Redis_Tests_Cache_FixesUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_Cache($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Cache
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$backend = $this->createCacheInstance($name);
|
||||
|
||||
$this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline");
|
||||
|
||||
return $backend;
|
||||
}
|
||||
|
||||
public function testTemporaryCacheExpire()
|
||||
{
|
||||
global $conf; // We are in unit tests so variable table does not exist.
|
||||
|
||||
$backend = $this->getBackend();
|
||||
|
||||
// Permanent entry.
|
||||
$backend->set('test1', 'foo', CACHE_PERMANENT);
|
||||
$data = $backend->get('test1');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foo', $data->data);
|
||||
|
||||
// Permanent entries should not be dropped on clear() call.
|
||||
$backend->clear();
|
||||
$data = $backend->get('test1');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foo', $data->data);
|
||||
|
||||
// Expiring entry with permanent default lifetime.
|
||||
$conf['cache_lifetime'] = 0;
|
||||
$backend->set('test2', 'bar', CACHE_TEMPORARY);
|
||||
sleep(2);
|
||||
$data = $backend->get('test2');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('bar', $data->data);
|
||||
sleep(2);
|
||||
$data = $backend->get('test2');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('bar', $data->data);
|
||||
|
||||
// Expiring entry with negative lifetime.
|
||||
$backend->set('test3', 'baz', time() - 100);
|
||||
$data = $backend->get('test3');
|
||||
$this->assertEqual(false, $data);
|
||||
|
||||
// Expiring entry with short lifetime.
|
||||
$backend->set('test4', 'foobar', time() + 2);
|
||||
$data = $backend->get('test4');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foobar', $data->data);
|
||||
sleep(4);
|
||||
$data = $backend->get('test4');
|
||||
$this->assertEqual(false, $data);
|
||||
|
||||
// Expiring entry with short default lifetime.
|
||||
$conf['cache_lifetime'] = 1;
|
||||
$backend->refreshMaxTtl();
|
||||
$backend->set('test5', 'foobaz', CACHE_TEMPORARY);
|
||||
$data = $backend->get('test5');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foobaz', $data->data);
|
||||
sleep(3);
|
||||
$data = $backend->get('test5');
|
||||
$this->assertEqual(false, $data);
|
||||
}
|
||||
|
||||
public function testDefaultPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
unset($conf['redis_perm_ttl']);
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(Redis_Cache::LIFETIME_PERM_DEFAULT, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testUserSetDefaultPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "3 months";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(7776000, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testUserSetPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "1 months";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(2592000, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testGetMultiple()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('multiple1', 1);
|
||||
$backend->set('multiple2', 2);
|
||||
$backend->set('multiple3', 3);
|
||||
$backend->set('multiple4', 4);
|
||||
|
||||
$cidList = array('multiple1', 'multiple2', 'multiple3', 'multiple4', 'multiple5');
|
||||
$ret = $backend->getMultiple($cidList);
|
||||
|
||||
$this->assertEqual(1, count($cidList));
|
||||
$this->assertFalse(isset($cidList[0]));
|
||||
$this->assertFalse(isset($cidList[1]));
|
||||
$this->assertFalse(isset($cidList[2]));
|
||||
$this->assertFalse(isset($cidList[3]));
|
||||
$this->assertTrue(isset($cidList[4]));
|
||||
|
||||
$this->assertEqual(4, count($ret));
|
||||
$this->assertTrue(isset($ret['multiple1']));
|
||||
$this->assertTrue(isset($ret['multiple2']));
|
||||
$this->assertTrue(isset($ret['multiple3']));
|
||||
$this->assertTrue(isset($ret['multiple4']));
|
||||
$this->assertFalse(isset($ret['multiple5']));
|
||||
}
|
||||
|
||||
public function testPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "2 seconds";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(2, $backend->getPermTtl());
|
||||
|
||||
$backend->set('test6', 'cats are mean');
|
||||
$this->assertIdentical('cats are mean', $backend->get('test6')->data);
|
||||
|
||||
sleep(3);
|
||||
$item = $backend->get('test6');
|
||||
$this->assertTrue(empty($item));
|
||||
}
|
||||
|
||||
public function testClearAsArray()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('test7', 1);
|
||||
$backend->set('test8', 2);
|
||||
$backend->set('test9', 3);
|
||||
|
||||
$backend->clear(array('test7', 'test9'));
|
||||
|
||||
$item = $backend->get('test7');
|
||||
$this->assertTrue(empty($item));
|
||||
$item = $backend->get('test8');
|
||||
$this->assertEqual(2, $item->data);
|
||||
$item = $backend->get('test9');
|
||||
$this->assertTrue(empty($item));
|
||||
}
|
||||
|
||||
public function testGetMultipleAlterCidsWhenCacheHitsOnly()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
$backend->clear('*', true); // It seems that there are leftovers.
|
||||
|
||||
$backend->set('mtest1', 'pouf');
|
||||
|
||||
$cids_partial_hit = array('foo' => 'mtest1', 'bar' => 'mtest2');
|
||||
$entries = $backend->getMultiple($cids_partial_hit);
|
||||
$this->assertIdentical(1, count($entries));
|
||||
// Note that the key is important because the method should
|
||||
// keep the keys synchronized.
|
||||
$this->assertEqual(array('bar' => 'mtest2'), $cids_partial_hit);
|
||||
|
||||
$backend->clear('mtest1');
|
||||
|
||||
$cids_no_hit = array('cat' => 'mtest1', 'dog' => 'mtest2');
|
||||
$entries = $backend->getMultiple($cids_no_hit);
|
||||
$this->assertIdentical(0, count($entries));
|
||||
$this->assertEqual(array('cat' => 'mtest1', 'dog' => 'mtest2'), $cids_no_hit);
|
||||
}
|
||||
}
|
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_Cache_FlushUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_Cache($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Cache
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$backend = $this->createCacheInstance($name);
|
||||
|
||||
$this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline");
|
||||
|
||||
return $backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that with a default cache lifetime temporary non expired
|
||||
* items are kept even when in temporary flush mode.
|
||||
*/
|
||||
public function testFlushIsTemporaryWithLifetime()
|
||||
{
|
||||
$GLOBALS['conf']['cache_lifetime'] = 112;
|
||||
|
||||
$backend = $this->getBackend();
|
||||
|
||||
// Even though we set a flush mode into this bin, Drupal default
|
||||
// behavior when a cache_lifetime is set is to override the backend
|
||||
// one in order to keep the core behavior and avoid potential
|
||||
// nasty bugs.
|
||||
$this->assertFalse($backend->allowTemporaryFlush());
|
||||
|
||||
$backend->set('test7', 42, CACHE_PERMANENT);
|
||||
$backend->set('test8', 'foo', CACHE_TEMPORARY);
|
||||
$backend->set('test9', 'bar', time() + 1000);
|
||||
|
||||
$backend->clear();
|
||||
|
||||
$cache = $backend->get('test7');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 42);
|
||||
$cache = $backend->get('test8');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 'foo');
|
||||
$cache = $backend->get('test9');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that with no default cache lifetime all temporary items are
|
||||
* droppped when in temporary flush mode.
|
||||
*/
|
||||
public function testFlushIsTemporaryWithoutLifetime()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$this->assertTrue($backend->allowTemporaryFlush());
|
||||
|
||||
$backend->set('test10', 42, CACHE_PERMANENT);
|
||||
// Ugly concatenation with the mode, but it will be visible in tests
|
||||
// reports if the entry shows up, thus allowing us to know which real
|
||||
// test case is run at this time
|
||||
$backend->set('test11', 'foo' . $backend->isSharded(), CACHE_TEMPORARY);
|
||||
$backend->set('test12', 'bar' . $backend->isSharded(), time() + 10);
|
||||
|
||||
$backend->clear();
|
||||
|
||||
$cache = $backend->get('test10');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 42);
|
||||
$this->assertFalse($backend->get('test11'));
|
||||
|
||||
$cache = $backend->get('test12');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
}
|
||||
|
||||
public function testNormalFlushing()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
$backendUntouched = $this->getBackend();
|
||||
|
||||
// Set a few entries.
|
||||
$backend->set('test13', 'foo');
|
||||
$backend->set('test14', 'bar', CACHE_TEMPORARY);
|
||||
$backend->set('test15', 'baz', time() + 3);
|
||||
|
||||
$backendUntouched->set('test16', 'dog');
|
||||
$backendUntouched->set('test17', 'cat', CACHE_TEMPORARY);
|
||||
$backendUntouched->set('test18', 'xor', time() + 5);
|
||||
|
||||
// This should not do anything (bugguy command)
|
||||
$backend->clear('', true);
|
||||
$backend->clear('', false);
|
||||
$this->assertNotIdentical(false, $backend->get('test13'));
|
||||
$this->assertNotIdentical(false, $backend->get('test14'));
|
||||
$this->assertNotIdentical(false, $backend->get('test15'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test16'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test17'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test18'));
|
||||
|
||||
// This should clear every one, permanent and volatile
|
||||
$backend->clear('*', true);
|
||||
$this->assertFalse($backend->get('test13'));
|
||||
$this->assertFalse($backend->get('test14'));
|
||||
$this->assertFalse($backend->get('test15'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test16'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test17'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test18'));
|
||||
}
|
||||
|
||||
public function testPrefixDeletionWithSeparatorChar()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('testprefix10', 'foo');
|
||||
$backend->set('testprefix11', 'foo');
|
||||
$backend->set('testprefix:12', 'bar');
|
||||
$backend->set('testprefix:13', 'baz');
|
||||
$backend->set('testnoprefix14', 'giraffe');
|
||||
$backend->set('testnoprefix:15', 'elephant');
|
||||
|
||||
$backend->clear('testprefix:', true);
|
||||
$this->assertFalse($backend->get('testprefix:12'));
|
||||
$this->assertFalse($backend->get('testprefix:13'));
|
||||
// @todo Temporary fix
|
||||
// At the moment shard enabled backends will erase all data instead
|
||||
// of just removing by prefix, so those tests won't pass
|
||||
if (!$backend->isSharded()) {
|
||||
$this->assertNotIdentical(false, $backend->get('testprefix10'));
|
||||
$this->assertNotIdentical(false, $backend->get('testprefix11'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix14'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix:15'));
|
||||
}
|
||||
|
||||
$backend->clear('testprefix', true);
|
||||
$this->assertFalse($backend->get('testprefix10'));
|
||||
$this->assertFalse($backend->get('testprefix11'));
|
||||
// @todo Temporary fix
|
||||
// At the moment shard enabled backends will erase all data instead
|
||||
// of just removing by prefix, so those tests won't pass
|
||||
if (!$backend->isSharded()) {
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix14'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix:15'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testOrder()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$id = 'speedtest' . $i;
|
||||
$backend->set($id, 'somevalue');
|
||||
$this->assertNotIdentical(false, $backend->get($id));
|
||||
$backend->clear('*', true);
|
||||
// Value created the same second before is dropped
|
||||
$this->assertFalse($backend->get($id));
|
||||
$backend->set($id, 'somevalue');
|
||||
// Value created the same second after is kept
|
||||
$this->assertNotIdentical(false, $backend->get($id));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FixesUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FlushUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Client_UnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Redis client manager',
|
||||
'description' => 'Tests Redis module client manager feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
|
||||
public function getManager()
|
||||
{
|
||||
return new Redis_Client_Manager(
|
||||
new Redis_Tests_Client_MockFactory(),
|
||||
array(
|
||||
'default' => array(),
|
||||
'foo' => array(
|
||||
'host' => 'foo.com',
|
||||
'port' => 666,
|
||||
),
|
||||
'bar' => array(
|
||||
'host' => 'bar.com',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testManagerServerList()
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
$defaultClient = $manager->getClient();
|
||||
$this->assertTrue(is_object($defaultClient));
|
||||
|
||||
// Ensure defaults are OK
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_HOST, $defaultClient->host);
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $defaultClient->port);
|
||||
$this->assertFalse(property_exists($defaultClient, 'base'));
|
||||
$this->assertFalse(property_exists($defaultClient, 'password'));
|
||||
|
||||
$client = $manager->getClient('foo');
|
||||
$this->assertIdentical('foo.com', $client->host);
|
||||
$this->assertIdentical(666, $client->port);
|
||||
|
||||
$client = $manager->getClient('bar');
|
||||
$this->assertIdentical('bar.com', $client->host);
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $client->port);
|
||||
|
||||
$this->assertIdentical($defaultClient, $manager->getClient('non_existing'));
|
||||
|
||||
try {
|
||||
$manager->getClient('other_non_existing', false);
|
||||
$this->assert(false);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->assert(true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Client_MockFactory implements Redis_Client_FactoryInterface
|
||||
{
|
||||
public function getClient($options = array())
|
||||
{
|
||||
return (object)$options;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'Mock';
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_Lock_LockingUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* Ensure lock flush at tear down
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $backends = array();
|
||||
|
||||
/**
|
||||
* Get the lock client class name
|
||||
*
|
||||
* @return string
|
||||
* Lock backend class name or null if cannot spawn it
|
||||
*/
|
||||
abstract protected function getLockBackendClass();
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if (!empty($this->backends)) {
|
||||
foreach ($this->backends as $backend) {
|
||||
$backend->lockReleaseAll();
|
||||
}
|
||||
|
||||
$this->backends = array();
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new lock backend with a generated lock id
|
||||
*
|
||||
* @return Redis_Lock_BackendInterface
|
||||
*/
|
||||
public function createLockBackend()
|
||||
{
|
||||
if (!$this->getLockBackendClass()) {
|
||||
throw new \Exception("Lock backend class does not exist");
|
||||
}
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK);
|
||||
|
||||
return $this->backends[] = new $className(
|
||||
Redis_Client::getClient(),
|
||||
Redis_Client::getDefaultPrefix('lock')
|
||||
);
|
||||
}
|
||||
|
||||
public function testLock()
|
||||
{
|
||||
$b1 = $this->createLockBackend();
|
||||
$b2 = $this->createLockBackend();
|
||||
|
||||
$s = $b1->lockAcquire('test1', 20000);
|
||||
$this->assertTrue($s, "Lock test1 acquired");
|
||||
|
||||
$s = $b1->lockAcquire('test1', 20000);
|
||||
$this->assertTrue($s, "Lock test1 acquired a second time by the same thread");
|
||||
|
||||
$s = $b2->lockAcquire('test1', 20000);
|
||||
$this->assertFalse($s, "Lock test1 could not be acquired by another thread");
|
||||
|
||||
$b2->lockRelease('test1');
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertFalse($s, "Lock test1 could not be released by another thread");
|
||||
|
||||
$b1->lockRelease('test1');
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertTrue($s, "Lock test1 has been released by the first thread");
|
||||
}
|
||||
|
||||
public function testReleaseAll()
|
||||
{
|
||||
$b1 = $this->createLockBackend();
|
||||
$b2 = $this->createLockBackend();
|
||||
|
||||
$b1->lockAcquire('test1', 200);
|
||||
$b1->lockAcquire('test2', 2000);
|
||||
$b1->lockAcquire('test3', 20000);
|
||||
|
||||
$s = $b2->lockAcquire('test2');
|
||||
$this->assertFalse($s, "Lock test2 could not be released by another thread");
|
||||
$s = $b2->lockAcquire('test3');
|
||||
$this->assertFalse($s, "Lock test4 could not be released by another thread");
|
||||
|
||||
$b1->lockReleaseAll();
|
||||
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertTrue($s, "Lock test1 has been released");
|
||||
$s = $b2->lockAcquire('test2');
|
||||
$this->assertTrue($s, "Lock test2 has been released");
|
||||
$s = $b2->lockAcquire('test3');
|
||||
$this->assertTrue($s, "Lock test3 has been released");
|
||||
|
||||
$b2->lockReleaseAll();
|
||||
}
|
||||
|
||||
public function testConcurentLock()
|
||||
{
|
||||
/*
|
||||
* Code for web test case
|
||||
*
|
||||
$this->drupalGet('redis/acquire/test1/1000');
|
||||
$this->assertText("REDIS_ACQUIRED", "Lock test1 acquired");
|
||||
|
||||
$this->drupalGet('redis/acquire/test1/1');
|
||||
$this->assertText("REDIS_FAILED", "Lock test1 could not be acquired by a second thread");
|
||||
|
||||
$this->drupalGet('redis/acquire/test2/1000');
|
||||
$this->assertText("REDIS_ACQUIRED", "Lock test2 acquired");
|
||||
|
||||
$this->drupalGet('redis/acquire/test2/1');
|
||||
$this->assertText("REDIS_FAILED", "Lock test2 could not be acquired by a second thread");
|
||||
*/
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Lock_LockingUnitTestCase')) {
|
||||
require_once(__DIR__ . '/LockingUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Lock_PhpRedisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis Redis locking',
|
||||
'description' => 'Ensure that Redis locking feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLockBackendClass()
|
||||
{
|
||||
return 'Redis_Lock_PhpRedis';
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Lock_PredisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis Redis locking',
|
||||
'description' => 'Ensure that Redis locking feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLockBackendClass()
|
||||
{
|
||||
return 'Redis_Lock_Predis';
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bugfixes made over time test class.
|
||||
*/
|
||||
abstract class Redis_Tests_Path_PathUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Path_HashLookupInterface
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_PATH);
|
||||
$hashLookup = new $className(Redis_Client::getClient(), 'path', Redis_Client::getDefaultPrefix('path'));
|
||||
|
||||
return $hashLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic functionnality
|
||||
*/
|
||||
public function testPathLookup()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
|
||||
$backend->saveAlias('node/1', 'node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr', $alias);
|
||||
|
||||
// Delete and ensure it does not exist anymore.
|
||||
$backend->deleteAlias('node/1', 'node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
|
||||
// Set more than one aliases and ensure order at loading.
|
||||
$backend->saveAlias('node/1', 'node-1-fr-1', 'fr');
|
||||
$backend->saveAlias('node/1', 'node-1-fr-2', 'fr');
|
||||
$backend->saveAlias('node/1', 'node-1-fr-3', 'fr');
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr-3', $alias);
|
||||
|
||||
// Add another alias to test the delete language feature.
|
||||
// Also add some other languages aliases.
|
||||
$backend->saveAlias('node/1', 'node-1');
|
||||
$backend->saveAlias('node/2', 'node-2-en', 'en');
|
||||
$backend->saveAlias('node/3', 'node-3-ca', 'ca');
|
||||
|
||||
// Ok, delete fr and tests every other are still there.
|
||||
$backend->deleteLanguage('fr');
|
||||
$alias = $backend->lookupAlias('node/1');
|
||||
$this->assertIdentical('node-1', $alias);
|
||||
$alias = $backend->lookupAlias('node/2', 'en');
|
||||
$this->assertIdentical('node-2-en', $alias);
|
||||
$alias = $backend->lookupAlias('node/3', 'ca');
|
||||
$this->assertIdentical('node-3-ca', $alias);
|
||||
|
||||
// Now create back a few entries in some langage and
|
||||
// ensure fallback to no language also works.
|
||||
$backend->saveAlias('node/4', 'node-4');
|
||||
$backend->saveAlias('node/4', 'node-4-es', 'es');
|
||||
$alias = $backend->lookupAlias('node/4');
|
||||
$this->assertIdentical('node-4', $alias);
|
||||
$alias = $backend->lookupAlias('node/4', 'es');
|
||||
$this->assertIdentical('node-4-es', $alias);
|
||||
$alias = $backend->lookupAlias('node/4', 'fr');
|
||||
$this->assertIdentical('node-4', $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests https://www.drupal.org/node/2728831
|
||||
*/
|
||||
public function testSomeEdgeCaseFalseNegative()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->deleteLanguage('fr');
|
||||
$backend->deleteLanguage('und');
|
||||
$backend->saveAlias('node/123', 'node-123');
|
||||
|
||||
// Language lookup should return the language neutral value if no value
|
||||
$source = $backend->lookupSource('node-123', 'fr');
|
||||
$this->assertIdentical($source, 'node/123');
|
||||
$source = $backend->lookupAlias('node/123', 'fr');
|
||||
$this->assertIdentical($source, 'node-123');
|
||||
|
||||
// Now, let's consider we have an item we don't know if it exists or
|
||||
// not, per definition we should not return a strict FALSE but a NULL
|
||||
// value instead to tell "we don't know anything about this". In a
|
||||
// very specific use-case, if the language neutral value is a strict
|
||||
// "not exists" value, it should still return NULL instead of FALSE
|
||||
// if another language was asked for.
|
||||
|
||||
// Store "value null" for the language neutral entry
|
||||
$backend->saveAlias('node/456', Redis_Path_HashLookupInterface::VALUE_NULL);
|
||||
$source = $backend->lookupAlias('node/456');
|
||||
$this->assertIdentical(false, $source);
|
||||
|
||||
$source = $backend->lookupAlias('node/456', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that lookup is case insensitive
|
||||
*/
|
||||
public function testCaseInsensitivePathLookup()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->saveAlias('node/1', 'Node-1-FR', 'fr');
|
||||
$source = $backend->lookupSource('NODE-1-fr', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$source = $backend->lookupSource('node-1-FR', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr', strtolower($alias));
|
||||
|
||||
// Delete and ensure it does not exist anymore.
|
||||
$backend->deleteAlias('node/1', 'node-1-FR', 'fr');
|
||||
$source = $backend->lookupSource('Node-1-FR', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Path_PathUnitTestCase')) {
|
||||
require_once(__DIR__ . '/PathUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Path_PhpRedisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis path inc replacement',
|
||||
'description' => 'Tests PhpRedis path inc replacement.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Path_PredisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis path inc replacement',
|
||||
'description' => 'Tests Predis path inc replacement.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Queue_QueueUnitTestCase')) {
|
||||
require_once(__DIR__ . '/QueueUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Queue_PhpRedisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis Redis queue',
|
||||
'description' => 'Ensure that Redis queue feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
class Redis_Tests_Queue_PredisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis Redis queue',
|
||||
'description' => 'Ensure that Redis queue feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
||||
*/
|
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Some tests in there credits goes to the redis_queue module.
|
||||
* Thanks to their author.
|
||||
*/
|
||||
abstract class Redis_Tests_Queue_QueueUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Redis_Queue
|
||||
*/
|
||||
public $queue;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
module_load_include('inc', 'system', 'system.queue');
|
||||
|
||||
$this->name = 'redis-queue-test-' . time();
|
||||
$this->queue = new Redis_Queue($this->name);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->queue->deleteQueue();
|
||||
$this->name = null;
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$res = $this->queue->createItem('test-queue-item-create');
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(1, $num);
|
||||
}
|
||||
|
||||
public function testClaim()
|
||||
{
|
||||
$data = 'test-queue-item-claimed';
|
||||
$res = $this->queue->createItem($data);
|
||||
$item = $this->queue->claimItem();
|
||||
$this->assertEqual($data, $item->data);
|
||||
}
|
||||
|
||||
/*
|
||||
public function testClaimBlocking()
|
||||
{
|
||||
$data = 'test-queue-item-claimed';
|
||||
$res = $this->queue->createItem($data);
|
||||
$this->assertTrue($res);
|
||||
$item = $this->queue->claimItemBlocking(10);
|
||||
$this->assertEqual($data, $item->data);
|
||||
}
|
||||
*/
|
||||
|
||||
public function testRelease()
|
||||
{
|
||||
$data = 'test-queue-item';
|
||||
|
||||
$res = $this->queue->createItem($data);
|
||||
$item = $this->queue->claimItem();
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(0, $num);
|
||||
|
||||
$res = $this->queue->releaseItem($item);
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(1, $num);
|
||||
}
|
||||
|
||||
public function testOrder()
|
||||
{
|
||||
$keys = array('test1', 'test2', 'test3');
|
||||
foreach ($keys as $k) {
|
||||
$this->queue->createItem($k);
|
||||
}
|
||||
|
||||
$first = $this->queue->claimItem();
|
||||
$this->assertEqual($first->data, $keys[0]);
|
||||
|
||||
$second = $this->queue->claimItem();
|
||||
$this->assertEqual($second->data, $keys[1]);
|
||||
|
||||
$this->queue->releaseItem($first);
|
||||
|
||||
$third = $this->queue->claimItem();
|
||||
$this->assertEqual($third->data, $keys[2]);
|
||||
|
||||
$first_again = $this->queue->claimItem();
|
||||
$this->assertEqual($first_again->data, $keys[0]);
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(0, $num);
|
||||
}
|
||||
|
||||
/*
|
||||
public function lease()
|
||||
{
|
||||
$data = 'test-queue-item';
|
||||
$res = $this->queue->createItem($data);
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEquals(1, $num);
|
||||
$item = $this->queue->claimItem(1);
|
||||
// In Redis 2.4 the expire could be between zero to one seconds off.
|
||||
sleep(2);
|
||||
$expired = $this->queue->expire();
|
||||
$this->assertEquals(1, $expired);
|
||||
$this->assertEquals(1, $this->queue->numberOfItems());
|
||||
// Create a second queue to test expireAll()
|
||||
$q2 = new RedisQueue($this->name . '_2');
|
||||
$q2->createItem($data);
|
||||
$q2->createItem($data);
|
||||
$this->assertEquals(2, $q2->numberOfItems());
|
||||
$item = $this->queue->claimItem(1);
|
||||
$item2 = $q2->claimItem(1);
|
||||
$this->assertEquals(1, $q2->numberOfItems());
|
||||
sleep(2);
|
||||
$expired = $this->queue->expireAll();
|
||||
$this->assertEquals(2, $expired);
|
||||
$this->assertEquals(1, $this->queue->numberOfItems());
|
||||
$this->assertEquals(2, $q2->numberOfItems());
|
||||
$q2->deleteQueue();
|
||||
}
|
||||
*/
|
||||
}
|
Reference in New Issue
Block a user