File Coverage

blib/lib/Parallel/Prefork/SpareWorkers/Scoreboard.pm
Criterion Covered Total %
statement 60 81 74.0
branch 13 42 30.9
condition 1 3 33.3
subroutine 15 18 83.3
pod 0 5 0.0
total 89 149 59.7


line stmt bran cond sub pod time code
1             package Parallel::Prefork::SpareWorkers::Scoreboard;
2              
3 1     1   3 use strict;
  1         1  
  1         24  
4 1     1   3 use warnings;
  1         1  
  1         29  
5              
6 1     1   3 use Fcntl qw(:DEFAULT :flock);
  1         1  
  1         390  
7 1     1   5 use File::Temp qw();
  1         1  
  1         13  
8 1     1   462 use POSIX qw(SEEK_SET);
  1         4258  
  1         4  
9 1     1   1182 use Scope::Guard;
  1         287  
  1         32  
10 1     1   407 use Signal::Mask;
  1         112150  
  1         32  
11              
12 1     1   7 use Parallel::Prefork::SpareWorkers qw(:status);
  1         1  
  1         132  
13              
14             # format of each slot: STATUS_CHAR PID(15bytes,left-aligned) "\n"
15 1     1   5 use constant SLOT_SIZE => 16;
  1         1  
  1         82  
16 1     1   4 use constant EMPTY_SLOT => STATUS_NEXIST . (' ' x (SLOT_SIZE - 2)) . "\n";
  1         1  
  1         775  
17             sub _format_slot {
18 0     0   0 my ($state, $pid) = @_;
19 0         0 substr($state, 0, 1) . sprintf "%-14d\n", $pid;
20             }
21              
22             sub new {
23 1     1 0 8 my ($klass, $filename, $max_workers) = @_;
24             # create scoreboard file
25 1   33     6 $filename ||= File::Temp::tempdir(CLEANUP => 1) . '/scoreboard';
26 1 50       410 sysopen my $fh, $filename, O_RDWR | O_CREAT | O_EXCL
27             or die "failed to create scoreboard file:$filename:$!";
28 1         24 my $wlen = syswrite $fh, EMPTY_SLOT x $max_workers;
29 1 50       4 die "failed to initialize scoreboad file:$filename:$!"
30             unless $wlen == SLOT_SIZE * $max_workers;
31 1         4 my $self = bless {
32             filename => $filename,
33             fh => $fh,
34             max_workers => $max_workers,
35             slot => undef,
36             }, $klass;
37 1         10 $self;
38             }
39              
40             sub get_statuses {
41 99     99 0 1463 local ($Signal::Mask{CHLD}, $Signal::Mask{TERM}, $Signal::Mask{INT}) = (1, 1, 1);
42              
43 99         8898 my $self = shift;
44 99 50       65813 sysseek $self->{fh}, 0, SEEK_SET
45             or die "seek failed:$!";
46             sysread($self->{fh}, my $sb, $self->{max_workers} * SLOT_SIZE)
47 99 50       858 == $self->{max_workers} * SLOT_SIZE
48             or die "failed to read status:$!";
49             my @s = map {
50 99 50       463 $_ =~ /^(.)/ ? ($1) : ()
  990         2998  
51             } split /\n/, $sb;
52             }
53              
54             sub clear_child {
55 10     10 0 150 local ($Signal::Mask{CHLD}, $Signal::Mask{TERM}, $Signal::Mask{INT}) = (1, 1, 1);
56              
57 10         944 my ($self, $pid) = @_;
58 10         25 my $lock = $self->_lock_file;
59 10 50       151 sysseek $self->{fh}, 0, SEEK_SET
60             or die "seek failed:$!";
61 10         34 for (my $slot = 0; $slot < $self->{max_workers}; $slot++) {
62 55         167 my $rlen = sysread($self->{fh}, my $data, SLOT_SIZE);
63 55 50       77 die "unexpected eof while reading scoreboard file:$!"
64             unless $rlen == SLOT_SIZE;
65 55 100       455 if ($data =~ /^.$pid[ ]*\n$/) {
66             # found
67 10 50       36 sysseek $self->{fh}, SLOT_SIZE * $slot, SEEK_SET
68             or die "seek failed:$!";
69 10         138 my $wlen = syswrite $self->{fh}, EMPTY_SLOT;
70 10 50       20 die "failed to clear scoreboard file:$self->{filename}:$!"
71             unless $wlen == SLOT_SIZE;
72 10         53 last;
73             }
74             }
75             }
76              
77             sub child_start {
78 0     0 0 0 local ($Signal::Mask{CHLD}, $Signal::Mask{TERM}, $Signal::Mask{INT}) = (1, 1, 1);
79              
80 0         0 my $self = shift;
81             die "child_start cannot be called twite"
82 0 0       0 if defined $self->{slot};
83             close $self->{fh}
84 0 0       0 or die "failed to close scoreboard file:$!";
85 0 0       0 sysopen $self->{fh}, $self->{filename}, O_RDWR
86             or die "failed to create scoreboard file:$self->{filename}:$!";
87 0         0 my $lock = $self->_lock_file;
88 0         0 for ($self->{slot} = 0;
89             $self->{slot} < $self->{max_workers};
90             $self->{slot}++) {
91 0         0 my $rlen = sysread $self->{fh}, my $data, SLOT_SIZE;
92 0 0       0 die "unexpected response from sysread:$rlen, expected @{[SLOT_SIZE]}:$!"
  0         0  
93             if $rlen != SLOT_SIZE;
94 0 0       0 if ($data =~ /^.[ ]+\n$/o) {
95 0         0 last;
96             }
97             }
98             die "no empty slot in scoreboard"
99 0 0       0 if $self->{slot} >= $self->{max_workers};
100 0         0 $self->set_status(STATUS_IDLE);
101             }
102              
103             sub set_status {
104 0     0 0 0 my ($self, $status) = @_;
105             die "child_start not called?"
106 0 0       0 unless defined $self->{slot};
107 0 0       0 sysseek $self->{fh}, $self->{slot} * SLOT_SIZE, SEEK_SET
108             or die "seek failed:$!";
109 0         0 my $wlen = syswrite $self->{fh}, _format_slot($status, $$);
110 0 0       0 die "failed to write status into scoreboard:$!"
111             unless $wlen == SLOT_SIZE;
112             }
113              
114             sub _lock_file {
115 10     10   16 my $self = shift;
116 10         15 my $fh = $self->{fh};
117 10 50       103 flock $fh, LOCK_EX
118             or die "failed to lock scoreboard file:$!";
119             return Scope::Guard->new(
120             sub {
121 10 50   10   172 flock $fh, LOCK_UN
122             or die "failed to unlock scoreboard file:$!";
123             },
124 10         112 );
125             }
126              
127             1;