1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
use strict;
use warnings;
use Test::More;
use File::Spec;
use File::Temp qw( tempdir );
use Log::Dispatch;
use Log::Dispatch::File::Locked;
use POSIX qw( :sys_wait_h );
use Try::Tiny;
my $ChildCount = 10;
for my $close_after_write ( 0, 1 ) {
my @v = _run_children($close_after_write);
subtest(
"close_after_write = $close_after_write",
sub {
_test_file_locked(@v);
}
);
}
done_testing();
sub _run_children {
my $close_after_write = shift;
my $dir = tempdir( CLEANUP => 1 );
my $file = File::Spec->catfile( $dir, 'lock-test.log' );
my $logger = _dispatch_for_file( $close_after_write, $file );
my %pids;
for ( 1 .. $ChildCount ) {
if ( my $pid = fork ) {
$pids{$pid} = 1;
}
else {
_write_to_file( $close_after_write, $file );
exit 0;
}
}
my %exit_status;
try {
local $SIG{ALRM}
= sub { die 'Waited 30 seconds for children to exit' };
alarm 30;
while ( keys %pids ) {
my $pid = waitpid( -1, WNOHANG );
if ( delete $pids{$pid} ) {
$exit_status{$pid} = $?;
}
}
};
return ( $file, $@, \%exit_status );
}
sub _write_to_file {
my $close_after_write = shift;
my $file = shift;
my $dispatch = _dispatch_for_file( $close_after_write, $file );
# The sleep makes a deadlock much more likely if the locking logic is not
# working correctly. Without it each child process runs so quickly that
# they are unlikely to step on each other.
$dispatch->info(1);
sleep 1;
$dispatch->info(2);
$dispatch->info(3);
return;
}
sub _dispatch_for_file {
my $close_after_write = shift;
my $file = shift;
return Log::Dispatch->new(
outputs => [
[
'File::Locked',
filename => $file,
mode => 'append',
close_after_write => $close_after_write,
min_level => 'debug',
newline => 1,
]
],
);
}
sub _test_file_locked {
my $file = shift;
my $exc = shift;
my $exits = shift;
is(
$exc,
q{},
'no exception forking children and writing to file'
);
is(
keys %{$exits},
$ChildCount,
"$ChildCount children exited",
);
for my $pid ( keys %{$exits} ) {
is(
$exits->{$pid},
0,
"$pid exited with 0"
);
}
_test_file_content($file);
}
sub _test_file_content {
my $file = shift;
open my $fh, '<', $file
or die "Cannot read $file: $!";
my @lines;
while ( defined( my $line = <$fh> ) ) {
chomp $line;
push @lines, $line;
}
close $fh or die $!;
return if is_deeply(
[ sort @lines ],
[ (1) x $ChildCount, (2) x $ChildCount, (3) x $ChildCount ],
'file contains expected content'
);
open my $diag_fh, '<', $file or die $!;
diag(
do { local $/ = undef; <$diag_fh> }
);
close $diag_fh or die $!;
}
|