AnyEvent-Redis-Federated
view release on metacpan or search on metacpan
lib/AnyEvent/Redis/Federated.pm view on Meta::CPAN
return 1 if $self->{server_status}{"$server:down"};
return 0;
}
sub isServerUp {
my ($self, $server) = @_;
return 0 if $self->{server_status}{"$server:down"};
return 1;
}
sub nextServer {
my ($self, $server, $node) = @_;
return $server unless $self->{config}->{nodes}->{$node}->{addresses};
$self->{config}->{nodes}->{$node}->{address} = shift(@{$self->{config}->{nodes}->{$node}->{addresses}});
push @{$self->{config}->{nodes}->{$node}->{addresses}}, $self->{config}->{nodes}->{$node}->{address};
warn "redis server for $node changed from $server to $self->{config}->{nodes}->{$node}->{address} selected\n" if $self->{debug};
return $self->{config}->{nodes}->{$node}->{address};
}
## return only on-line/up servers?
sub allServers {
my ($self, $node) = @_;
my $hosts = [ grep { $self->isServerUp($_) } @{$self->{config}->{nodes}->{$node}->{addresses}} ];
return $hosts;
}
sub markServerUp {
my ($self, $server) = @_;
if ($self->{server_status}{"$server:down"}) {
my $down_since = localtime($self->{server_status}{"$server:down_since"});
delete $self->{server_status}{"$server:down"};
delete $self->{server_status}{"$server:retries"};
delete $self->{server_status}{"$server:down_since"};
delete $self->{server_status}{"$server:retry_pending"};
delete $self->{server_status}{"$server:retry_interval"};
warn "redis server $server back up (down since $down_since)\n" if $self->{debug};
}
return 1;
}
sub markServerDown {
my ($self, $server, $delay) = @_;
$delay ||= $self->{base_retry_interval};
warn "redis server $server seems down\n" if $self->{debug};
# first time?
if (not $self->{server_status}{"$server:down"}) {
warn "server $server down, first time\n" if $self->{debug};
$self->{server_status}{"$server:down"} = 1;
$self->{server_status}{"$server:retries"}++;
$self->{server_status}{"$server:down_since"} = time();
$self->{server_status}{"$server:retry_interval"} ||= $self->{base_retry_interval};
}
if ($self->{server_status}{"$server:retry_pending"}) {
warn "retry already pending for $server, skipping\n" if $self->{debug};
return 1;
}
# ok, schedule the timer to re-check. this should NOT be a
# recurring timer, otherwise we end up with a bunch of pending
# retries since the "interval" is likely shorter than TCP timeout.
# eventually this will error out, in which case we'll try it again
# by calling markServerDown() after clearying retry_pending, or
# it'll work and we're good to go.
my $t;
my $r;
$t = AnyEvent->timer(
after => $delay,
cb => sub {
warn "timer callback triggered for $server" if $self->{debug};
my ($host, $port) = split /:/, $server;
print "attempting reconnect to $server\n" if $self->{debug};
$r = AnyEvent::Redis->new(
host => $host,
port => $port,
on_error => sub {
warn @_ unless $self->{quiet};
$self->{server_status}{"$server:retry_pending"} = 0;
$self->markServerDown($server); # schedule another try
}
);
$r->ping(sub{
my $val = shift; # should be 'PONG'
if ($val ne 'PONG') {
warn "retry ping got $val instead of PONG" if $self->{debug};
}
$self->{conn}->{$server} = $r;
$self->{server_status}{"$server:retry_pending"} = 0;
$self->markServerUp($server);
undef $t; # we need to keep a ref to the timer here so it runs at all
});
}
);
warn "scheduled health check of $server in $delay secs\n" if $self->{debug};
$self->{server_status}{"$server:retry_pending"} = 1;
return 1;
}
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $call = lc $AUTOLOAD;
$call =~ s/.*:://;
print "method [$call] autoloaded\n" if $self->{debug};
my $key = $_[0];
my $hk = $key;
my $cb = sub { };
if (ref $_[-1] eq 'CODE') {
$cb = pop @_;
}
# key group?
if (ref($_[0]) eq 'ARRAY') {
$hk = $_[0]->[0];
$key = $_[0]->[1];
$_[0] = $key;
}
my $node = $self->keyToNode($hk);
my $query_all = $self->{query_all};
if ($call =~ s/_all$//) {
$query_all = 1;
}
## The normal single-server case...
if (not $query_all) {
my $server = $self->nodeToHost($node);
print "server [$server] of node [$node] for key [$key] hashkey [$hk]\n" if $self->{debug};
if ($self->isServerDown($server)) {
# try another if we can
if ($self->{config}->{nodes}->{$node}->{addresses}) {
print "server [$server] seems down\n" if $self->{debug};
$server = $self->nextServer($server, $node);
print "trying next server in line [$server] for node [$node]\n" if $self->{debug};
}
# bail otherwise
else {
print "server $server down. abandoning call.\n" if $self->{debug};
$cb->(undef);
return $self;
}
}
return $self->scheduleCall($server, $call, [@_], $cb);
}
## Need to fire this one at all up servers in the node group...
else {
my $servers = $self->allServers($node);
for my $server (@$servers) {
$self->scheduleCall($server, $call, [@_], $cb);
}
return $self;
}
}
sub poll {
my ($self) = @_;
#return if $self->{pending_requests} < 1;
return if not defined $self->{cv};
my $rid = $self->{request_serial};
my $timeout = $self->{command_timeout};
my $w;
if ($timeout) {
$w = AnyEvent->signal(signal => "ALRM", cb => sub {
warn "AnyEvent::Redis::Federated::poll alarm timeout! ($rid)\n" if $self->{debug};
# check the state of requests, marking remaining as cancelled
while (my ($rid, $state) = each %{$self->{request_state}}) {
if ($self->{request_state}->{$rid}) {
print "found pending request to cancel: $rid\n" if $self->{debug};
$self->{request_state}->{$rid} = 0;
$self->{cv}->end;
undef $w;
}
}
});
print "scheduling alarm timer in poll() for $timeout\n" if $self->{debug};
alarm($timeout);
}
$self->{cv}->recv;
$self->{cv} = undef;
alarm(0);
undef $w;
}
sub scheduleCall {
my ($self, $server, $call, $args, $cb) = @_;
# have a non-idle connection already?
my $r;
if ($self->{conn}->{$server}) {
if ($self->{idle_timeout}) {
if ($self->{last_used}->{$server} > time - $self->{idle_timeout}) {
$r = $self->{conn}->{$server};
}
}
else {
$r = $self->{conn}->{$server};
}
}
# otherwise create a new connection
if (not defined $r) {
my ($host, $port) = split /:/, $server;
print "attempting new connection to $server\n" if $self->{debug};
$r = AnyEvent::Redis->new(
host => $host,
port => $port,
on_error => sub {
warn @_ unless $self->{quiet};
$self->markServerDown($server);
$self->{cv}->end;
}
);
$self->{conn}->{$server} = $r;
}
if (not defined $self->{cv}) {
$self->{cv} = AnyEvent->condvar;
}
$self->{cv}->begin;
$self->{request_serial}++;
my $rid = $self->{request_serial};
$self->{request_state}->{$rid} = 1; # open request; 0 is cancelled
print "scheduling request $rid: $_[0]\n" if $self->{debug};
if ($call eq 'multi' or $call eq 'exec') {
@$args = (); # these don't really take args
}
$r->$call(@$args, sub {
if (not $self->{request_state}->{$rid}) {
print "call found request $rid cancelled\n" if $self->{debug};
delete $self->{request_state}->{$rid};
( run in 0.518 second using v1.01-cache-2.11-cpan-6aa56a78535 )