2010-10-29 47 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