异常后重试操作:请批评我的代码

时间:2009-07-01 21:51:59

标签: perl exception sqlite cpan

我的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 } );
                }
            }
        } );
};

有没有更好的方法来实现这个?我是在重新发明轮子吗?是 我应该使用CPAN上的代码吗?

4 个答案:

答案 0 :(得分:4)

我可能倾向于像这样写重试:

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 $@";
}

它与现有代码的工作方式不同,但有一些优点。

  1. 我摆脱了*10的事情,就像另一张海报一样,我无法辨别其目的。
  2. 此函数可以将$func()所做的任何值返回给其调用者。
  3. 在语义上,代码更类似于你正在做的事情,至少对我迷茫的思想而言。
  4. _retry 0, sub { };仍将执行一次,但与现有版本不同,永远不会重试,永远不会执行sub。
  5. 更多建议(但稍微不那么理性)的抽象:

    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, job_id => $job->job_id }, { rows => $self->{batchsize} } ],
        update => [ { state => 1 } ],
      );
    };
    

    这些也可能是您代码的便利添加。 (未经测试)

答案 1 :(得分:3)

我看到的唯一真正的问题是缺乏最后的陈述。我就是这样写的:

sub _retry {
    my ($timeout, $func) = @_;
    for my $try (0 .. $timeout*10) {
        sleep 0.1 if $try;
        eval { $func->(); 1 } or do {
            next if $@ =~ /database is locked/; #ignore this error
            croak $@;                           #but raise any other error
        };
        last;
    }
}

答案 2 :(得分:1)

我可能会使用'return'而不​​是'last'(在Chas Owens修改的代码中),但净效果是相同的。我也不清楚为什么要将重试函数的第一个参数乘以10。

IMNSHO,将(重新)将常见的骨架代码分解为函数要好得多,而不是一遍又一遍地连续编写相同的代码片段。太危险了:

  • 你必须改变逻辑 - 在很多地方
  • 您忘记在某些时候正确编辑逻辑

这些是支持使用函数或等效内联代码抽象的标准参数。

换句话说 - 创造功能的好工作。 Perl允许您动态创建函数(感谢Larry)非常有用!

答案 3 :(得分:0)

马克福勒的

Attempt似乎与我上面描述的非常接近。现在,如果可以指定某种异常过滤器,那将会很方便。