4

我的 Perl 应用程序使用的资源有时会暂时不可用,从而导致使用die. 最值得注意的是,它访问由多个线程共享的 SQLite 数据库,以及使用 through 与其他应用程序共享的 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 $@;

...所以我将它们放入(特定于数据库访问的)函数中:

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

4 回答 4

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 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 } ],
  );
};

这些也可能是对您的代码的方便补充。(未经测试)

于 2009-07-01T23:51:01.910 回答
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;
    }
}
于 2009-07-01T22:04:02.110 回答
1

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

IMNSHO,像您所做的那样将常见的骨架代码(重新)分解到一个函数中要比不断地一遍又一遍地编写相同的代码片段要好得多。有太多的危险:

  • 你必须改变逻辑——在太多的地方
  • 您有时忘记正确编辑逻辑

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

换句话说 - 在创建功能方面做得很好。Perl 允许您动态创建函数很有用(感谢 Larry)!

于 2009-07-01T22:48:06.860 回答
0

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

于 2009-07-02T22:59:11.197 回答