2010-10-29 44 views
2

如何測試資源(基於文件的緩存,用於在Perl中緩存Web應用程序的輸出)在對所述共享資源的併發訪問下行爲合理?如何在Perl中測試資源(緩存)的併發訪問?

我寫了一個簡單的基於文件的緩存,用Perl編寫,它使用鎖定序列化寫入訪問,即只有一個進程(重新)生成緩存條目。這個緩存用於緩存Perl webapp(gitweb)的輸出,如果它很重要的話。

我想測試一下,表示緩存在併發訪問下的行爲是否健全,例如只有一個進程會運行用於生成緩存的子例程($cache->compute($key, sub { ... })),所有進程都會獲得生成的數據,如果進程寫入緩存進入死亡它不會死鎖進程等待緩存(重新)生成等。

我應該怎麼做?有沒有一個可以使用的Perl模塊?

回答

1

在我根據我的Unix for Perl programmers: pipes and processes亞倫起重機工作結束;儘管在這些筆記中,他簡化了一些不處理從多個進程中讀取而沒有鎖定的內容(在這些筆記中臨時文件用於第二個流)。

的代碼只使用Test::More,沒有非核心Perl模塊

 
#!/usr/bin/perl 

use warnings; 
use strict; 

use POSIX qw(dup2); 
use Fcntl qw(:DEFAULT); 
use IO::Handle; 
use IO::Select; 
use IO::Pipe; 

use Test::More; 

# [...] 

# from http://aaroncrane.co.uk/talks/pipes_and_processes/ 
sub fork_child (&) { 
    my ($child_process_code) = @_; 

    my $pid = fork(); 
    die "Failed to fork: $!\n" if !defined $pid; 

    return $pid if $pid != 0; 

    # Now we're in the new child process 
    $child_process_code->(); 
    exit; 
} 

sub parallel_run (&) { 
    my $child_code = shift; 
    my $nchildren = 2; 

    my %children; 
    my (%pid_for_child, %fd_for_child); 
    my $sel = IO::Select->new(); 
    foreach my $child_idx (1..$nchildren) { 
     my $pipe = IO::Pipe->new() 
      or die "Failed to create pipe: $!\n"; 

     my $pid = fork_child { 
      $pipe->writer() 
       or die "$$: Child \$pipe->writer(): $!\n"; 
      dup2(fileno($pipe), fileno(STDOUT)) 
       or die "$$: Child $child_idx failed to reopen stdout to pipe: $!\n"; 
      close $pipe 
       or die "$$: Child $child_idx failed to close pipe: $!\n"; 

      # From Test-Simple-0.96/t/subtest/fork.t 
      # 
      # Force all T::B output into the pipe (redirected to STDOUT), 
      # for the parent builder as well as the current subtest builder. 
      { 
       no warnings 'redefine'; 
       *Test::Builder::output   = sub { *STDOUT }; 
       *Test::Builder::failure_output = sub { *STDOUT }; 
       *Test::Builder::todo_output = sub { *STDOUT }; 
      } 

      $child_code->(); 

      *STDOUT->flush(); 
      close(STDOUT); 
     }; 

     $pid_for_child{$pid} = $child_idx; 
     $pipe->reader() 
      or die "Failed to \$pipe->reader(): $!\n"; 
     $fd_for_child{$pipe} = $child_idx; 
     $sel->add($pipe); 

     $children{$child_idx} = { 
      'pid' => $pid, 
      'stdout' => $pipe, 
      'output' => '', 
     }; 
    } 

    while (my @ready = $sel->can_read()) { 
     foreach my $fh (@ready) { 
      my $buf = ''; 
      my $nread = sysread($fh, $buf, 1024); 

      exists $fd_for_child{$fh} 
       or die "Cannot find child for fd: $fh\n"; 

      if ($nread > 0) { 
       $children{$fd_for_child{$fh}}{'output'} .= $buf; 
      } else { 
       $sel->remove($fh); 
      } 
     } 
    } 

    while (%pid_for_child) { 
     my $pid = waitpid -1, 0; 
     warn "Child $pid_for_child{$pid} ($pid) failed with status: $?\n" 
      if $? != 0; 
     delete $pid_for_child{$pid}; 
    } 

    return map { $children{$_}{'output'} } keys %children; 
} 

# [...] 

@output = parallel_run { 
    my $data = $cache->compute($key, \&get_value_slow); 
    print $data; 
}; 
is_deeply(
    \@output, 
    [ ($value) x 2 ], 
    'valid data returned by both process' 
); 
0

有兩個過程:

  • 寫出來的時候訪問之前。
  • 嘗試訪問
  • 睡眠5秒鎖定
  • 解除鎖定並寫入時間。

它應該需要一個過程的兩倍時間的另一個。

至於測試當進程死亡時它是否清除。代替die。或者如果這是非常黑的方塊,請啓動一個線程,當您期望進程鎖定時調用exit

但是,我不知道你是如何導致整個過程從單線程睡覺。

+0

一切都是白色盒子 - 這是我自己的代碼。我總是可以從測試中分離出來,但問題在於收集來自兒童的數據。 – 2010-10-29 19:30:42

0

我會使用Test :: Class和Test :: Exception作爲創建測試的基礎結構。

...例如只有一個進程 將運行用於生成 緩存($的cache>計算($鍵,子{...} ))

應該子程序可能成爲這樣的事情:

sub test_inter_process_mutex { 
    # spawn process to acquire a lock, capture the pid 
    # assert I except when trying to acquire the lock 
    # send HUP signal to process, process releases lock and dies 
} 

所有進程會得到產生 數據

這個更難。我可能會嘗試隔離通信機制並斷言它以某種方式工作。

,如果進程寫入緩存條目 死了,就不會發生死鎖進程 等待緩存(重新)產生 等。

變爲:

sub test_no_process_deathgrip { 
    # spawn process to acquire the lock and then except 
    # assert I can acquire the lock 

    # for signals HUP, SIGINT, TERM, and KILL 
    # spawn a process to acquire the lock, capture pid 
    # send signal to process 
    # assert I can acquire the lock 
} 

}

+0

可能會使用[Test :: Routine](http://p3rl.org/Test::Routine)代替Test :: Class(如果使用的是Moose)和[Test :: Fatal](http:/ /p3rl.org/Test::Fatal)代替Test :: Exception ...如果不是因爲我更喜歡只使用核心Perl模塊。 – 2010-11-03 15:47:19