[TESTS][CACHE] Fixup errors found in cache implementation by testing. Ensure the newest values are kept, in pushList with max_count

This commit is contained in:
Hugo Sales 2021-07-20 14:06:29 +00:00
parent e2f61b05d8
commit 28c010fcc7
Signed by: someonewithpc
GPG Key ID: 7D0C7EAFC9D835A0
1 changed files with 27 additions and 16 deletions

View File

@ -49,30 +49,34 @@ abstract class Cache
self::$pools[$pool] = []; self::$pools[$pool] = [];
self::$redis[$pool] = []; self::$redis[$pool] = [];
foreach (explode(',', $val) as $dsn) { foreach (explode(',', $val) as $dsn) {
list($scheme, $rest) = explode('://', $dsn); if (str_contains($dsn, '://')) {
$partial_to_dsn = function ($r) use ($scheme) { return $scheme . '://' . $r; }; [$scheme, $rest] = explode('://', $dsn);
} else {
$scheme = $dsn;
$rest = '';
}
switch ($scheme) { switch ($scheme) {
case 'redis': case 'redis':
// Redis can have multiple servers, but we want to take proper advantage of // Redis can have multiple servers, but we want to take proper advantage of
// redis, not just as a key value store, but using it's datastructures // redis, not just as a key value store, but using it's datastructures
$dsns = explode(';', $rest); $dsns = explode(';', $dsn);
if (count($dsns) === 1) { if (count($dsns) === 1) {
$class = Redis::class; $class = Redis::class;
$r = new Redis(); $r = new Redis();
if ($rest[0] != '/' && strstr($rest, ':') != false) { $r->pconnect($rest);
list($host, $port) = explode(':', $rest);
$r->pconnect($host, $port);
} else {
$r->pconnect($rest);
}
} else { } else {
if (strstr($rest, ':') == false) { // @codeCoverageIgnoreStart
// This requires extra server configuration, but the code was tested
// manually and works, so it'll be excluded from automatic tests, for now, at least
if (F\Every($dsns, function ($str) { [$scheme, $rest] = explode('://', $str); return str_contains($rest, ':'); }) == false) {
throw new ConfigurationException('The configuration of a redis cluster requires specifying the ports to use'); throw new ConfigurationException('The configuration of a redis cluster requires specifying the ports to use');
} }
$class = RedisCluster::class; // true for persistent connection $class = RedisCluster::class; // true for persistent connection
$r = new RedisCluster(null, $dsns, timeout: null, read_timeout: null, persistent: true); $seeds = F\Map($dsns, fn ($str) => explode('://', $str)[1]);
$r = new RedisCluster(name: null, seeds: $seeds, timeout: null, read_timeout: null, persistent: true);
// Distribute reads randomly // Distribute reads randomly
$r->setOption($class::OPT_SLAVE_FAILOVER, $class::FAILOVER_DISTRIBUTE); $r->setOption($class::OPT_SLAVE_FAILOVER, $class::FAILOVER_DISTRIBUTE);
// @codeCoverageIgnoreEnd
} }
// Improved serializer // Improved serializer
$r->setOption($class::OPT_SERIALIZER, $class::SERIALIZER_MSGPACK); $r->setOption($class::OPT_SERIALIZER, $class::SERIALIZER_MSGPACK);
@ -84,8 +88,11 @@ abstract class Cache
$adapters[$pool][] = new Adapter\RedisAdapter($r); $adapters[$pool][] = new Adapter\RedisAdapter($r);
break; break;
case 'memcached': case 'memcached':
// @codeCoverageIgnoreStart
// These all are excluded from automatic testing, as they require an unreasonable amount
// of configuration in the testing environment. The code is really simple, so it should work
// memcached can also have multiple servers // memcached can also have multiple servers
$dsns = F\map(explode(',', $rest), $partial_to_dsn); $dsns = explode(';', $dsn);
$adapters[$pool][] = new Adapter\MemcachedAdapter($dsns); $adapters[$pool][] = new Adapter\MemcachedAdapter($dsns);
break; break;
case 'filesystem': case 'filesystem':
@ -103,6 +110,7 @@ abstract class Cache
default: default:
Log::error("Unknown or discouraged cache scheme '{$scheme}'"); Log::error("Unknown or discouraged cache scheme '{$scheme}'");
return; return;
// @codeCoverageIgnoreEnd
} }
} }
@ -113,7 +121,7 @@ abstract class Cache
if (count($adapters[$pool]) === 1) { if (count($adapters[$pool]) === 1) {
self::$pools[$pool] = array_pop($adapters[$pool]); self::$pools[$pool] = array_pop($adapters[$pool]);
} else { } else {
self::$pools[$pool] = new ChainAdapter($adapters); self::$pools[$pool] = new ChainAdapter($adapters[$pool]);
} }
} }
} }
@ -147,10 +155,12 @@ abstract class Cache
Log::info('Item "{key}" elected for early recomputation', ['key' => $key]); Log::info('Item "{key}" elected for early recomputation', ['key' => $key]);
} else { } else {
if ($recompute = ($idletime = self::$redis[$pool]->object('idletime', $key) ?? false) && ($expiry = self::$redis[$pool]->ttl($key) ?? false) && $expiry <= $idletime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) { if ($recompute = ($idletime = self::$redis[$pool]->object('idletime', $key) ?? false) && ($expiry = self::$redis[$pool]->ttl($key) ?? false) && $expiry <= $idletime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) {
// @codeCoverageIgnoreStart
Log::info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ Log::info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
'key' => $key, 'key' => $key,
'delta' => sprintf('%.1f', $expiry - microtime(true)), 'delta' => sprintf('%.1f', $expiry - microtime(true)),
]); ]);
// @codeCoverageIgnoreEnd
} }
} }
} }
@ -186,7 +196,7 @@ abstract class Cache
->del($key) ->del($key)
->rPush($key, ...$value) ->rPush($key, ...$value)
// trim to $max_count, unless it's 0 // trim to $max_count, unless it's 0
->lTrim($key, 0, $max_count != null ? $max_count : -1) ->lTrim($key, -$max_count ?? 0, -1)
->exec(); ->exec();
} else { } else {
self::set($key, $value, $pool, $beta); self::set($key, $value, $pool, $beta);
@ -204,13 +214,14 @@ abstract class Cache
->multi(Redis::PIPELINE) ->multi(Redis::PIPELINE)
->rPush($key, $value) ->rPush($key, $value)
// trim to $max_count, if given // trim to $max_count, if given
->lTrim($key, 0, $max_count ?? -1) ->lTrim($key, -$max_count ?? 0, -1)
->exec(); ->exec();
} else { } else {
$res = self::get($key, function () { return []; }, $pool, $beta); $res = self::get($key, function () { return []; }, $pool, $beta);
$res[] = $value; $res[] = $value;
if ($max_count != null) { if ($max_count != null) {
$res = array_slice($res, 0, $max_count); $count = count($res);
$res = array_slice($res, $count - $max_count, $count); // Trim the older values
} }
self::set($key, $res, $pool, $beta); self::set($key, $res, $pool, $beta);
} }