#!perl
use warnings;
use strict;
use Test::More;
use Test::Fatal;
use Time::HiRes qw(gettimeofday tv_interval);
use Redis::Fast;
use lib 't/tlib';
use Test::SpawnRedisServer;
use Net::EmptyPort qw(empty_port);
my ($c, $srv) = redis(timeout => 1);
END { $c->() if $c }
subtest 'Command without connection, no reconnect' => sub {
ok(my $r = Redis::Fast->new(reconnect => 0, server => $srv), 'connected to our test redis-server');
ok($r->quit, 'close connection to the server');
like(exception { $r->set(reconnect => 1) }, qr{Not connected to any server}, 'send ping without reconnect',);
};
subtest 'Command without connection or timeout, with database change, with reconnect' => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
ok($r->select(4), 'send command with reconnect');
ok($r->set(reconnect => $$), 'send command with reconnect');
ok($r->quit, 'close connection to the server');
is($r->get('reconnect'), $$, 'reconnect with read errors before write');
};
subtest 'Reconnection discards pending commands' => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
my $processed_pending = 0;
$r->dbsize(sub { $processed_pending++ });
_wait_for_redis_timeout();
ok($r->set(foo => 'bar'), 'send command with reconnect');
is($processed_pending, 0, 'pending command discarded on reconnect');
};
subtest 'INFO commands with extra logic triggers reconnect' => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
ok($r->quit, 'close connection to the server');
my $info = $r->info;
is(ref $info, 'HASH', 'reconnect on INFO command');
};
subtest 'KEYS commands with extra logic triggers reconnect' => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
ok($r->flushdb, 'delete all keys');
ok($r->set(reconnect => $$), 'set known key');
ok($r->quit, 'close connection to the server');
my @keys = $r->keys('*');
is_deeply(\@keys, ['reconnect'], 'reconnect on KEYS command');
};
subtest 'PING commands with extra logic triggers reconnect' => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
_wait_for_redis_timeout();
my $res = $r->ping;
ok($res, 'reconnect on PING command');
ok($r->quit, 'close connection to the server');
$res = $r->ping;
ok(!$res, 'QUIT command disables reconnect on PING command');
};
subtest "Bad commands don't trigger reconnect" => sub {
ok(my $r = Redis::Fast->new(reconnect => 2, server => $srv), 'connected to our test redis-server');
my $prev_sock = $r->__sock;
like(
exception { $r->set(bad => reconnect => 1) },
qr{ERR wrong number of arguments for 'set' command|ERR syntax error},
'Bad commands still die',
);
is($r->__sock, $prev_sock, "... and don't trigger a reconnect");
};
subtest 'Reconnect code clears sockect ASAP' => sub {
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
_wait_for_redis_timeout();
is(exception { $r->quit }, undef, "Quit doesn't die if we are already disconnected");
};
subtest "Reconnect gives up after timeout" => sub {
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
$c->(); ## Make sure the server is dead
my $t0 = [gettimeofday];
like(
exception { $r->set(reconnect => 1) },
qr{Could not connect to Redis server at},
'Eventually it gives up and dies',
);
ok(tv_interval($t0) > 3, '... minimum value for the reconnect reached');
};
subtest "Reconnect during transaction" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
ok($r->multi(), 'start transacion');
ok($r->set('reconnect_1' => 1), 'set first key');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
like(exception { $r->set('reconnect_2' => 2) }, qr{reconnect disabled inside transaction}, 'set second key');
$r->connect(); #reconnect
is($r->exists('reconnect_1'), 0, 'key "reconnect_1" should not exist');
is($r->exists('reconnect_2'), 0, 'key "reconnect_2" should not exist');
};
subtest "exec does not trigger reconnect" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
ok($r->multi(), 'start transacion');
ok($r->set('reconnect_1' => 1), 'set first key');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
like(exception { $r->exec() }, qr{reconnect disabled inside transaction}, 'exec');
$r->connect(); #reconnect
is($r->exists('reconnect_1'), 0, 'key "reconnect_1" should not exist');
};
subtest "multi should trigger reconnect" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
ok($r->multi(), 'start transacion');
ok($r->set('reconnect' => 1), 'set key');
ok($r->exec(), 'execute transaction');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
ok($r->set('reconnect' => 1), 'setting key should not fail');
};
subtest "Reconnect works after WATCH + MULTI + EXEC" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
ok($r->set('watch' => 'watch'), 'set watch key');
ok($r->watch('watch'), 'start watching key');
ok($r->multi(), 'start transacion');
ok($r->set('reconnect' => 1), 'set key');
ok($r->exec(), 'execute transaction');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
ok($r->set('reconnect' => 1), 'setting key should not fail');
};
subtest "Reconnect works after WATCH + MULTI + DISCARD" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(reconnect => 3, server => $srv), 'connected to our test redis-server');
ok($r->set('watch' => 'watch'), 'set watch key');
ok($r->watch('watch'), 'start watching key');
ok($r->multi(), 'start transacion');
ok($r->set('reconnect' => 1), 'set key');
ok($r->discard(), 'dscard transaction');
$c->();
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
ok($r->set('reconnect' => 1), 'setting second key should not fail');
};
subtest "Reconnect behaviour differs from cpan Redis module #73" => sub {
$c->(); ## Make previous server is dead
my $port = empty_port();
ok(($c, $srv) = redis(port => $port, timeout => 1), "spawn redis on port $port");
ok(my $r = Redis::Fast->new(
reconnect => 1,
every => 100_000,
cnx_timeout => 3,
read_timeout => 1,
write_timeout => 1,
server => $srv
), 'connected to our test redis-server');
ok($r->set(reconnect => $$), 'send command');
$c->(); ## Make sure the server is dead
like(
exception { $r->set(reconnect => $$) },
qr{Could not connect to Redis server at},
'Eventually it gives up and dies (first try)',
);
like(
exception { $r->set(reconnect => $$) },
qr{Could not connect to Redis server at},
'Eventually it gives up and dies (second try)',
);
ok(($c, $srv) = redis(port => $port, timeout => 1), "respawn redis on port $port");
ok($r->set(reconnect => $$), 'a command works after respawn');
};
done_testing();
sub _wait_for_redis_timeout {
## Redis will timeout clients after 100 internal server loops, at
## least 10 seconds (even with a timeout 1 on the config) so we sleep
## a bit more hoping the timeout did happen. Not perfect, patches
## welcome
diag('Sleeping 11 seconds, waiting for Redis to timeout...');
sleep(11);
}