2009-07-01 32 views
4

我的Perl應用程序使用的資源有時暫時不可用,導致使用die的異常。最值得注意的是,它訪問由多個線程共享的SQLite數據庫以及其他使用DBIx::Class的應用程序。每當發生這種異常時,應該重試該操作直到達到超時。在發生異常後重試操作:請批評我的代碼

我喜歡簡潔的代碼,所以我很快就厭倦了反覆 鍵入7條額外的線路爲每個這樣的操作:

use Time::HiRes 'sleep'; 
use Carp; 

# [...] 

for (0..150) { 
    sleep 0.1 if $_; 
    eval { 
     # database access 
    }; 
    next if [email protected] =~ /database is locked/; 
} 
croak [email protected] if [email protected]; 

...所以我把它們放在一個(DB特定接入)功能:

sub _retry { 
    my ($timeout, $func) = @_; 
    for (0..$timeout*10) { 
     sleep 0.1 if $_; 
     eval { $func->(); }; 
     next if [email protected] =~ /database is locked/; 
    } 
    croak [email protected] if [email protected]; 
} 

我稱之爲是這樣的:

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

有AB etter的方式來實現這一點?我正在重新發明輪子嗎?我應該使用CPAN上的代碼 ?

+0

在這種情況下,Lambdas是改善代碼重用的好方法。儘管有時他們可以降低可讀性,但在這種情況下,他們實際上可以提高很多。 – 2009-07-03 06:30:28

+0

澄清:我在說,你已經在做什麼(== lambda)是好的。 – 2009-07-03 06:31:05

回答

3

我看到的唯一真正的問題是缺少最後一條語句。這是我會怎麼寫呢:

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

顯然,我忘了複製最後一個。這是我的原始代碼。感謝您指出了這一點。 – hillu 2009-07-01 22:21:05

1

我可能會使用的,而不是「最後」(在經修訂的查斯·歐文斯的代碼),但淨效果是一樣的「迴歸」。我也不清楚爲什麼你的重試函數的第一個參數乘以10.

IMNSHO,這是好得多(重新)因素,因爲你已經完成的常用骨骼代碼,而不是繼續寫相同的代碼一遍又一遍的片段。有太多的危險,即:

  • 你必須改變的邏輯 - 在太多的地方
  • 你忘記在某個時刻正確地編輯邏輯

這些都是贊成的標準參數使用函數或內聯代碼的等效抽象。

換句話說 - 在創建函數上做得很好。 Perl允許您即時創建函數(謝謝Larry)!

+1

超時時間以秒爲單位,他在兩次嘗試之間休眠0.1秒,所以1秒超時將有10次嘗試。 – 2009-07-02 00:20:49

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 [email protected] unless [email protected] =~ /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 [email protected]"; 
} 

它不相同的工作,以現有的代碼,但是有幾個優點。

  1. 我擺脫了*10的事情,就像另一張海報,我無法辨別它的目的。
  2. 這個函數能夠返回任何$func()對它的調用者的值。
  3. 從語義上講,代碼更類似於你在做什麼,至少對我那迷惑的頭腦來說。
  4. ​​仍然會執行一次,但不會重試,不像您現在的版本,永遠不會執行該子。

多個建議(但稍顯不足的理性)抽象:

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

這些也可能是得心應手補充你的代碼。 (未測試)

0

Attempt馬克福勒似乎非常接近我上面描述的。現在,如果可以指定某種異常過濾器,它會很方便。

相關問題