File Coverage

blib/lib/IPC/Semaphore/Concurrency.pm
Criterion Covered Total %
statement 58 101 57.4
branch 18 56 32.1
condition 2 19 10.5
subroutine 13 22 59.0
pod 11 12 91.6
total 102 210 48.5


line stmt bran cond sub pod time code
1             package IPC::Semaphore::Concurrency;
2              
3 1     1   6303 use 5.008008;
  1         4  
  1         39  
4 1     1   6 use strict;
  1         1  
  1         37  
5 1     1   17 use warnings;
  1         2  
  1         30  
6              
7 1     1   6 use Carp;
  1         1  
  1         85  
8 1     1   6 use POSIX qw(O_WRONLY O_CREAT O_NONBLOCK O_NOCTTY);
  1         1  
  1         11  
9 1     1   1056 use IPC::SysV qw(ftok IPC_NOWAIT IPC_CREAT IPC_EXCL S_IRUSR S_IWUSR S_IRGRP S_IWGRP S_IROTH S_IWOTH SEM_UNDO);
  1         6368  
  1         265  
10 1     1   4027 use IPC::Semaphore;
  1         16172  
  1         5645  
11              
12             require Exporter;
13             our @ISA = qw(Exporter);
14             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
15             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
16             our @EXPORT = qw();
17              
18             our $VERSION = '0.04';
19              
20             sub new {
21 2     2 0 1407 my $class = shift;
22              
23 2         5 my %args;
24 2 100       12 if (@_ == 1) {
25             # Only one required argument
26 1         4 $args{'path'} = shift;
27             } else {
28 1         7 %args = @_;
29             }
30              
31 2 50       10 if (!exists($args{'path'})) {
32 0         0 carp "Must supply a path!"; #TODO: Allow private semaphores
33 0         0 return undef;
34             }
35             # Set defaults
36 2 100       9 $args{'project'} = 0 if (!exists($args{'project'}));
37 2 100       7 $args{'count'} = 1 if (!exists($args{'count'}));
38 2 100       8 $args{'value'} = 1 if (!exists($args{'value'})); # TODO: allow array (one value per semaphore)
39 2 100       8 $args{'touch'} = 1 if (!exists($args{'touch'}));
40              
41 2         9 my $self = bless {}, $class;
42 2         18 $self->{'_args'} = { %args };
43              
44 2 0 33     251 $self->_touch($self->{'_args'}->{'path'}) if (!-e $self->{'_args'}->{'path'} || $self->{'_args'}->{'touch'}) or return undef;
      50        
45 2 50       7 $self->{'_args'}->{'key'} = $self->_ftok() or return undef;
46              
47 2 50       9 $self->{'_args'}->{'sem'} = $self->_create($self->key()) or return undef;
48              
49 2         10 return $self;
50             }
51              
52             # Internal functions
53             sub _touch {
54             # Create and/or touch the path, returns false if there's an error
55 2     2   5 my $self = shift;
56 2         5 my $path = shift;
57 2 50 0     243 sysopen(my $fh, $path, O_WRONLY|O_CREAT|O_NONBLOCK|O_NOCTTY) or carp "Can't create $path: $!" and return 0;
58 2 50       89 utime(undef, undef, $path) if ($self->{'_args'}->{'touch'});
59 2 50 0     32 close $fh or carp "Can't close $path: $!" and return 0;
60 2         9 return 1;
61             }
62              
63             sub _ftok {
64             # Create an IPC key, returns result of ftok()
65 2     2   4 my $self = shift;
66 2 0 0     69 return ftok($self->{'_args'}->{'path'}, $self->{'_args'}->{'project'}) or carp "Can't create semaphore key: $!" and return undef;
67             }
68              
69             sub _create {
70             # Create the semaphore and assign it its initial value
71 2     2   3 my $self = shift;
72 2         3 my $key = shift;
73             # Presubably the semaphore exists already, so try using it right away
74 2         15 my $sem = IPC::Semaphore->new($key, 0, 0);
75 2 50       34 if (!defined($sem)) {
76             # Creatie a new semaphore...
77 2         15 $sem = IPC::Semaphore->new($key, $self->{'_args'}->{'count'}, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
78 2 50       193 if (!defined($sem)) {
79             # Make sure another process did not create it in our back
80 0 0       0 $sem = IPC::Semaphore->new($key, 0, 0) or carp "Semaphore creation failed!\n";
81             } else {
82             # If we created the semaphore now we assign its initial value
83 2         11 for (my $i=0; $i<$self->{'_args'}->{'count'}; $i++) { # TODO: Support array - see above
84 21         277 $sem->op($i, $self->{'_args'}->{'value'}, 0);
85             }
86             }
87             }
88             # Return whatever last semget call got us
89 2         54 return $sem;
90             }
91              
92             # External API
93              
94             sub getall {
95 0     0 1 0 my $self = shift;
96 0         0 return $self->{'_args'}->{'sem'}->getall();
97             }
98              
99             sub getval {
100 0     0 1 0 my $self = shift;
101 0 0       0 my $nsem = shift or 0;
102 0         0 return $self->{'_args'}->{'sem'}->getval($nsem);
103             }
104              
105             sub getncnt {
106 0     0 1 0 my $self = shift;
107 0 0       0 my $nsem = shift or 0;
108 0         0 return $self->{'_args'}->{'sem'}->getncnt($nsem);
109             }
110              
111             sub setall {
112 0     0 1 0 my $self = shift;
113 0         0 return $self->{'_args'}->{'sem'}->setall(@_);
114             }
115              
116             sub setval {
117 0     0 1 0 my $self = shift;
118 0         0 my ($nsem, $val) = @_;
119 0         0 return $self->{'_args'}->{'sem'}->setval($nsem, $val);
120             }
121              
122             sub stat {
123 0     0 1 0 my $self = shift;
124 0         0 return $self->{'_args'}->{'sem'}->stat();
125             }
126              
127             sub id {
128 0     0 1 0 my $self = shift;
129 0         0 return $self->{'_args'}->{'sem'}->id();
130             }
131              
132             sub key {
133 2     2 1 4 my $self = shift;
134 2         12 return $self->{'_args'}->{'key'};
135             }
136              
137             sub acquire {
138 0     0 1 0 my $self = shift;
139              
140 0         0 my %args;
141 0 0 0     0 if (@_ >= 1 && $_[0] =~ /^\d+$/) {
142             # Positional arguments
143 0         0 ($args{'sem'}, $args{'wait'}, $args{'max'}, $args{'undo'}) = @_;
144             } else {
145 0         0 %args = @_;
146             }
147             # Defaults
148 0 0       0 $args{'sem'} = 0 if (!defined($args{'sem'}));
149 0 0       0 $args{'wait'} = 0 if (!defined($args{'wait'}));
150 0 0       0 $args{'max'} = -1 if (!defined($args{'max'}));
151 0 0       0 $args{'undo'} = 1 if (!defined($args{'undo'}));
152              
153 0         0 my $sem = $self->{'_args'}->{'sem'};
154 0         0 my $flags = IPC_NOWAIT;
155 0 0       0 $flags |= SEM_UNDO if ($args{'undo'});
156              
157 0         0 my ($ret, $ncnt);
158             # Get blocked process count here to retain Errno (thus $!) after the first semop call.
159 0 0       0 $ncnt = $self->getncnt($args{'sem'}) if ($args{'wait'});
160              
161 0 0       0 if (($ret = $sem->op($args{'sem'}, -1, $flags))) {
    0          
162 0         0 return $ret;
163             } elsif ($args{'wait'}) {
164 0 0 0     0 return $ret if ($args{'max'} >= 0 && $ncnt >= $args{'max'});
165             # Remove NOWAIT and block
166 0         0 $flags ^= IPC_NOWAIT;
167 0         0 return $sem->op($args{'sem'}, -1, $flags);
168             }
169 0         0 return $ret;
170             }
171              
172             sub release {
173 0     0 1 0 my $self = shift;
174 0   0     0 my $number = shift || 0;
175 0         0 return $self->{'_args'}->{'sem'}->op($number, 1, 0);
176             }
177              
178             sub remove {
179 2     2 1 1003 my $self = shift;
180 2         11 return $self->{'_args'}->{'sem'}->remove();
181             }
182              
183             1;
184             __END__