EV-ClickHouse
view release on metacpan or search on metacpan
t/20_reconnect.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use EV;
use EV::ClickHouse;
# auto_reconnect coverage:
# - basic plumbing (flag set, simple query)
# - pre-connect queue drain
# - real mid-session reconnect: HTTP keep_alive_timeout closes the server
# side after a few idle seconds; the client must recover and dispatch the
# next query on a fresh socket.
#
# ClickHouse native protocol has no SQL-level disconnect primitive in 26.x
# (no SYSTEM drop CONNECTION; KILL QUERY only kills queries), so a true
# native disconnect-recovery test would need a TCP proxy harness or
# tcp_close_connection_after_queries_seconds in server config â left as
# integration testing.
my $host = $ENV{TEST_CLICKHOUSE_HOST} || '127.0.0.1';
my $nat_port = $ENV{TEST_CLICKHOUSE_NATIVE_PORT} || 9000;
my $http_port = $ENV{TEST_CLICKHOUSE_PORT} || 8123;
require IO::Socket::INET;
my $nat_ok = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $nat_port, Timeout => 2) ? 1 : 0;
my $http_ok = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $http_port, Timeout => 2) ? 1 : 0;
plan skip_all => "ClickHouse not reachable" unless $nat_ok || $http_ok;
plan tests => 7;
SKIP: {
skip "Native port not reachable", 4 unless $nat_ok;
my $port = $nat_port;
# 1-2: basic auto_reconnect handshake â connecting and running one query
# with the flag set must work the same as without.
{
my $ch;
my ($rows, $err);
$ch = EV::ClickHouse->new(
host => $host,
port => $port,
protocol => 'native',
auto_reconnect => 1,
reconnect_delay => 0.1,
reconnect_max_delay => 1,
on_connect => sub {
$ch->query("select 42", sub {
($rows, $err) = @_;
EV::break;
});
},
on_error => sub { diag("error: $_[0]") },
);
my $t = EV::timer(10, 0, sub { EV::break });
EV::run;
ok(!$err && $rows, "auto_reconnect: simple query works")
or diag "err=" . ($err // '<undef>');
is($rows ? $rows->[0][0] : undef, 42, "auto_reconnect: result correct");
$ch->finish if $ch && $ch->is_connected;
}
# 3-4: queue queries during initial connect, verify they all dispatch.
{
my $ch;
my @results;
$ch = EV::ClickHouse->new(
host => $host,
port => $port,
protocol => 'native',
auto_reconnect => 1,
on_connect => sub {
# connect happened â pending queue should drain on its own
},
on_error => sub { diag("error: $_[0]") },
);
# Queue immediately, before on_connect can fire.
for my $i (1..3) {
$ch->query("select $i", sub {
my ($rows, $err) = @_;
push @results, $err ? "ERR: $err" : $rows->[0][0];
EV::break if @results == 3;
});
}
my $t = EV::timer(10, 0, sub { EV::break });
EV::run;
is(scalar @results, 3, "queued-pre-connect: all 3 callbacks fired");
is_deeply([sort @results], [1, 2, 3],
"queued-pre-connect: results match queries");
$ch->finish if $ch && $ch->is_connected;
}
} # end native SKIP block
# 5-7: real mid-session reconnect via HTTP keep_alive_timeout (default 3s).
# Run Q1, wait long enough for the server to drop the socket on idle, then
# run Q2 â auto_reconnect must re-establish on the fly. We arm the idle
# timer only once (on_connect fires on every reconnect, so guarding with a
# flag prevents us from looping forever on Q1).
SKIP: {
skip "HTTP port not reachable", 3 unless $http_ok;
my $ch;
my ($q1_err, $q2_err, $q2_val);
my $disconnects = 0;
my $first_connect = 1;
my $idle_timer;
$ch = EV::ClickHouse->new(
host => $host,
port => $http_port,
auto_reconnect => 1,
reconnect_delay => 0.1,
reconnect_max_delay => 1,
on_disconnect => sub { $disconnects++ },
on_error => sub { diag("HTTP reconnect error: $_[0]") },
on_connect => sub {
return unless $first_connect;
$first_connect = 0;
$ch->query("select 1 format TabSeparated", sub {
(undef, $q1_err) = @_;
# Idle past keep_alive_timeout so the server hangs up,
# then send Q2 â auto_reconnect must dispatch it onto a
# fresh socket.
$idle_timer = EV::timer(4, 0, sub {
$ch->query("select 2 format TabSeparated", sub {
my ($rows, $err) = @_;
$q2_err = $err;
$q2_val = ($rows && @$rows) ? $rows->[0][0] : undef;
EV::break;
});
});
});
},
);
my $watchdog = EV::timer(15, 0, sub { EV::break });
EV::run;
ok(!$q1_err, "HTTP reconnect: first query ok");
ok(!$q2_err, "HTTP reconnect: second query ok after server idle-close")
or diag "q2_err=" . ($q2_err // '<undef>');
is($q2_val, 2, "HTTP reconnect: second query result correct")
or diag sprintf("disconnects=%d, q2_val=%s",
$disconnects, $q2_val // '<undef>');
$ch->finish if $ch && $ch->is_connected;
}
( run in 1.703 second using v1.01-cache-2.11-cpan-4ef0a570458 )