我的Perl应用程序使用暂时不可用的资源,导致使用die的异常.最值得注意的是,它访问由多个线程共享的sqlite数据库以及通过DBIx :: Class使用的其他应用程序.每当发生此类异常时,都应重试该操作,直到达到超时.
我更喜欢简洁的代码,因此我很快就厌倦了
为每个这样的操作键入7个额外的行:
use Time::HiRes 'sleep'; use Carp; # [...] for (0..150) { sleep 0.1 if $_; eval { # database access }; next if $@ =~ /database is locked/; } croak $@ if $@;
…所以我将它们放入(特定于DB访问)功能:
sub _retry { my ( $timeout,$func ) = @_; for (0..$timeout*10) { sleep 0.1 if $_; eval { $func->(); }; next if $@ =~ /database is locked/; } croak $@ if $@; }
我称之为:
my @thingies; _retry 15,sub { $schema->txn_do( sub { @thingies = $thingie_rs->search( { state => 0,job_id => $job->job_id },{ rows => $self->{batchsize} } ); if (@thingies) { for my $thingie (@thingies) { $thingie->update( { state => 1 } ); } } } ); };
解决方法
我可能倾向于写这样的重试:
sub _retry { my ( $retrys,$func ) = @_; attempt: { my $result; # if it works,return the result return $result if eval { $result = $func->(); 1 }; # nah,it Failed,if failure reason is not a lock,croak croak $@ unless $@ =~ /database is locked/; # if we have 0 remaining retrys,stop trying. last attempt if $retrys < 1; # sleep for 0.1 seconds,and then try again. sleep 0.1; $retrys--; redo attempt; } croak "Attempts Exceeded $@"; }
它与现有代码的工作方式不同,但有一些优点.
>我摆脱了* 10的东西,就像另一张海报,我无法辨别其目的.
>此函数能够将$func()所做的值返回给其调用者.
>在语义上,代码更类似于你正在做的事情,至少对我迷茫的思想而言.
> _retry 0,sub {};仍将执行一次,但永远不会重试,不像你现在的版本,永远不会执行sub.
更多建议(但稍微不那么理性)的抽象:
sub do_update { my %params = @_; my @result; $params{schema}->txn_do( sub { @result = $params{rs}->search( @{ $params{search} } ); return unless (@result); for my $result_item (@result) { $result_item->update( @{ $params{update} } ); } } ); return \@result; } my $data = _retry 15,sub { do_update( schema => $schema,rs => $thingy_rs,search => [ { state => 0,{ rows => $self->{batchsize} } ],update => [ { state => 1 } ],); };
这些也可能是您代码的便利补充. (未经测试)