From 92a07f540a1abcda30b7685064a58c2cf159d81f Mon Sep 17 00:00:00 2001 From: Yuriy Nevinitsin <nevinitsin@corp.mail.ru> Date: Fri, 16 Mar 2012 13:06:23 +0400 Subject: [PATCH] [connector][perl] bug fix --- connector/perl/lib/MR/Pending.pm | 367 +++++++++++++++++++++++++ connector/perl/lib/MR/Tarantool/Box.pm | 4 +- connector/perl/t/box.pl | 6 +- 3 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 connector/perl/lib/MR/Pending.pm diff --git a/connector/perl/lib/MR/Pending.pm b/connector/perl/lib/MR/Pending.pm new file mode 100644 index 0000000000..fd3f6c9ba1 --- /dev/null +++ b/connector/perl/lib/MR/Pending.pm @@ -0,0 +1,367 @@ +package MR::Pending; +use Mouse; +use Time::HiRes qw/time/; + +has maxtime => ( + is => 'rw', + isa => 'Num', + predicate => "_has_maxtime", + default => 6.0, +); + +has itertime => ( + is => 'rw', + isa => 'Num', + predicate => "_has_itertime", + default => 0.1, +); + +has name => ( + is => 'rw', + isa => 'Str', + required => 1, +); + +has onidle => ( + is => 'rw', + isa => 'CodeRef', + predicate => "_has_onidle", +); + +has _pending => ( + is => 'ro', + isa => 'HashRef[MR::Pending::Item]', + default => sub { {} }, +); + +has exceptions => ( + is => 'rw', + isa => 'Int', + default => 0, +); + +has _exceptions => ( + is => 'ro', + isa => 'ArrayRef', + default => sub { [] }, +); + +has _waitresult => ( + is => 'rw', + isa => 'ArrayRef', +); + +around BUILDARGS => sub { + my $orig = shift; + my $class = shift; + my %args = @_; + if(my $p = delete $args{pending}) { + $args{_pending} = { map { $_->id => $_ } @$p }; + } + $class->$orig(%args); +}; + +sub runcatch { + my ($self, $code, @param) = @_; + my $ret; + unless(eval { $ret = &$code(@param); 1 }) { + push @{$self->_exceptions}, $@; + $self->exceptions($self->exceptions + 1); + } + return $ret; +} + +sub add { + my ($self, @p) = @_; + my $p = $self->_pending; + for my $add (@p) { + die if exists $p->{$add->id}; + $p->{$add->id} = $add; + } + return $self; +} + +sub remove { + my ($self, @p) = @_; + my $p = $self->_pending; + for my $del (@p) { + die unless exists $p->{$del->id}; + delete $p->{$del->id}; + } + return $self; +} + +sub send { + my ($self) = @_; + my $pending = $self->_pending; + foreach my $shard ( grep { $pending->{$_}->is_sleeping } keys %$pending ) { + my $pend = $pending->{$shard}; + if ($pend->try < $pend->retry) { + next unless $pend->is_timeout; + $pend->set_pending_mode(scalar $self->runcatch($pend->onretry, ($pend->id, $pend, $self))); + } else { + delete $pending->{$shard}; + $self->runcatch($pend->onerror, ($pend->id, "no success after @{[$pend->try]} retries", $pend, $self)); + } + } + return $self; +} + +sub wait { + my ($self) = @_; + my $pending = $self->_pending; + + my $rin = ''; + vec($rin, $_->fileno, 1) = 1 for grep { $_->is_pending } values %$pending; + my $ein = $rin; + + my $n = CORE::select($rin, undef, $ein, $self->itertime); + $self->_waitresult([$rin,$ein]); + if ($n < 0) { + warn $self->name.": select() failed: $!"; + return undef; + } + + if ($n == 0) { + $self->runcatch($self->onidle, ($self)) if $self->_has_onidle; + return 0; + } + + return $n; +} + +sub recv { + my ($self) = @_; + my $pending = $self->_pending; + my ($rin, $ein) = @{$self->_waitresult}; + + for my $shard (grep { $pending->{$_}->is_pending } keys %$pending) { + my $pend = $pending->{$shard}; + my $fileno = $pend->fileno; + if (vec($rin, $fileno, 1)) { + if (my $list = $pend->continue) { + if (ref $list) { + delete $pending->{$shard}; + $self->runcatch($pend->onok, ($pend->id, $list, $pend, $self)); + } + } else { + $pend->close("error while receiving"); + } + } elsif (vec($ein, $fileno, 1)) { + $pend->close("connection reset"); + } elsif ($pend->is_timeout) { + $pend->close("timeout"); + } + } + + return $self; +} + +sub finish { + my ($self) = @_; + my $timeout = !$self->exceptions; + my $pending = $self->_pending; + for my $shard (grep { !$pending->{$_}->is_done } keys %$pending) { + my $pend = delete $pending->{$shard}; + $pend->close($timeout ? "timeout" : "aborted due to external exception"); + $self->runcatch($pend->onerror, ($pend->id, "timeout", $pend, $self)) if $timeout; + } + return $self; +} + +sub iter { + my ($self) = @_; + + $self->send or return; + return if $self->exceptions; + + my $res = $self->wait; + return if $self->exceptions; + return unless defined $res; + return 1 unless $res; + + $self->recv or return; + return if $self->exceptions; + + return 1; +} + +sub work { + my ($self) = @_; + + my $pending = $self->_pending; + my $time0 = time; + + while(%$pending and time() - $time0 <= $self->maxtime) { + last unless $self->iter; + } + $self->finish; + $self->check_exceptions('raise'); +} + +sub check_exceptions { + my ($self, $raise) = @_; + my $e = $self->_exceptions; + return unless $e && @$e; + my $str = "$$: PENDING EXCEPTIONS BEGIN\n".join("\n$$:###################\n", @$e)."$$: PENDING EXCEPTIONS END"; + die $str if $raise; + warn $str if defined $raise; + return $str; +} + +no Mouse; +__PACKAGE__->meta->make_immutable(); + + + + +package MR::Pending::Item; +use Mouse; +use Time::HiRes qw/time/; +use Carp; + +has id => ( + is => 'ro', + isa => 'Str', + required => 1, +); + +has $_ => ( + is => 'ro', + isa => 'CodeRef', + predicate => "_has_$_", +) for qw/onok onerror onretry/; + +has $_ => ( + is => 'rw', + isa => 'Num', + predicate => "_has_$_", +) for qw/timeout retry_delay/; + +has retry => ( + is => 'rw', + isa => 'Int', + predicate => "_has_retry", +); + +has _done => ( + is => 'rw', + isa => 'Bool', + default => 0, +); + +has _time => ( + is => 'rw', + isa => 'Num', + default => 0, +); + +has _connection => ( + is => 'rw', + isa => 'Maybe[MR::IProto::Connection::Sync]', + clearer => '_clear__connection', + predicate=> '_has__connection', +); + +has fileno => ( + is => 'ro', + isa => 'Int', + lazy => 1, + default => sub { Carp::confess "not connected!" unless $_[0]->_connection; $_[0]->_connection->fh->fileno }, + clearer => '_clear_fileno', +); + +has _continue => ( + is => 'rw', + isa => 'Maybe[CodeRef]', + clearer => '_clear__continue', +); + +has _postprocess => ( + is => 'rw', + isa => 'Maybe[CodeRef]', + clearer => '_clear__postprocess', +); + +has try => ( + is => 'ro', + isa => 'Int', + default => 0, + writer => '_set_try', +); + +# has bornat => ( +# is => 'ro', +# isa => 'Str', +# default => sub { "[".join("-", $_[0], $$, time(), Carp::longmess())."]"; }, +# ); + +sub is_done { return $_[0]->_done } +sub is_pending { return !$_[0]->_done && $_[0]->_has__connection } +sub is_sleeping { return !$_[0]->_done && !$_[0]->_has__connection } + +sub set_pending_mode { + my ($self, $cont) = @_; + $self->_clear__connection; + $self->_clear__continue; + $self->_clear__postprocess; + $self->_clear_fileno; + if($cont) { + $self->_connection($cont->{connection}); + $self->_continue($cont->{continue}); + $self->_postprocess($cont->{postprocess}); + } + $self->_set_try($self->try + 1) if @_ > 1; + $self->_time(time); + return $self; +} + +sub set_sleeping_mode { + $_[0]->set_pending_mode; +} + +sub is_timeout { + my ($self, $timeout) = @_; + $timeout ||= $self->is_pending ? $self->timeout : $self->retry_delay; + return time() - $self->_time > $timeout; +} + +sub continue { + my ($self) = @_; + my $is_cont = 0; + my @list; + if (eval{@list = $self->_continue->($is_cont); 1}) { + if ($is_cont) { + $self->_clear_fileno; + $self->_connection($list[0]->{connection}); + $self->_continue($list[0]->{continue}); + $self->_time(time); + return 1; + } else { + $self->_done(1); + if (my $pp = $self->_postprocess) { + &$pp(\@list); + } + return \@list; + } + } else { + warn $@; + } + return 0; +} + +sub close { + my ($self, $reason) = @_; + $self->_connection->Close($reason) if $self->is_pending; + $self->set_sleeping_mode; +} + +sub DEMOLISH { + my ($self) = @_; + Carp::cluck "$$ FORGOTTEN $self" if $self->is_pending; +} + +no Mouse; +__PACKAGE__->meta->make_immutable(); + +1; diff --git a/connector/perl/lib/MR/Tarantool/Box.pm b/connector/perl/lib/MR/Tarantool/Box.pm index 1538c89294..8f5f83b725 100644 --- a/connector/perl/lib/MR/Tarantool/Box.pm +++ b/connector/perl/lib/MR/Tarantool/Box.pm @@ -79,7 +79,7 @@ use constant { sub IPROTOCLASS () { 'MR::IProto' } use vars qw/$VERSION %ERRORS/; -$VERSION = 0.0.17; +$VERSION = 0.0.18; BEGIN { *confess = \&MR::IProto::confess } @@ -968,7 +968,7 @@ sub Select { return unless $r; - return $r if $param->{hash_by}; + return $r if defined $param->{hash_by}; return $r if $param->{want} eq 'arrayref'; $wantarray = wantarray if $param->{return_fh}; diff --git a/connector/perl/t/box.pl b/connector/perl/t/box.pl index 4025819169..bb6941ffc6 100644 --- a/connector/perl/t/box.pl +++ b/connector/perl/t/box.pl @@ -13,7 +13,7 @@ use FindBin qw($Bin); use lib "$Bin"; use Carp qw/confess/; -use Test::More tests => 337; +use Test::More tests => 339; use Test::Exception; use List::MoreUtils qw/zip/; @@ -163,6 +163,10 @@ ok $box->Replace(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'repl is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/replace'; +is_deeply [$box->Select([13], {raise => 0, hash_by => 0, raw => 1})], [{13 => [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789']}], 'select/rawhash1'; +is_deeply [$box->Select([13,14], {raise => 0, hash_by => 0, raw => 1})], [{13 => [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 14 => [14, 'some1email@test.mail.ru', 1, 2, 3, 4, '123456789']}], 'select/rawhash2'; + + do { my $continuation = $box->Select(13,{ return_fh => 1 }); ok $continuation, "select/continuation"; -- GitLab