如何在Perl中测试对资源(缓存)的并发访问?

时间:2010-10-29 16:40:12

标签: perl testing caching concurrency locking

如何测试该资源(基于文件的缓存以便在Perl中缓存webapp的输出)在并发访问所述共享资源的情况下表现得非常明智?

我写了一个简单的基于文件的缓存,用Perl编写,它使用锁定来序列化写访问,即只有一个(重新)生成缓存条目的进程。如果重要的话,这个缓存将用于缓存Perl webapp(gitweb)的输出。

我想测试一下,所说的缓存在并发访问下表现得很好,例如只有一个进程会运行用于生成缓存的子例程($cache->compute($key, sub { ... })),所有进程都会获得生成的数据,如果进程写入缓存条目时,它不会使等待缓存(重新)生成等的进程死锁。

我该怎么办?我可以使用现成的Perl模块吗?

3 个答案:

答案 0 :(得分:1)

最后,我的工作基于Aaron Crane的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'
);

答案 1 :(得分:0)

有两个过程:

  • 写出访问前的时间。
  • 尝试访问
  • 使用锁
  • 睡5秒钟
  • 释放锁定并写下时间。

一个过程应该是另一个过程的两倍。

测试是否在过程死亡时清理。 die代替锁定。或者如果这是一个非常黑的框,当你希望进程拥有锁时,启动一个调用exit线程

但是,我不确定你是如何让整个过程从单个线程中休眠的。

答案 2 :(得分:0)

我会使用Test :: Class和Test :: Exception作为创建测试的基础结构。

  

...例如,只有一个进程   会运行用于生成的子程序   缓存($ cache-> compute($ key,sub {...   }))

应该可能变成这样:

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
}

}