forked from GNUsocial/gnu-social
[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:
parent
4f6f4aa512
commit
b79629b6d2
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user