File Coverage

blib/lib/IPC/Semaphore/SmokeSignals.pm
Criterion Covered Total %
statement 164 182 90.1
branch 54 90 60.0
condition 30 57 52.6
subroutine 30 32 93.7
pod 8 8 100.0
total 286 369 77.5


line stmt bran cond sub pod time code
1             package IPC::Semaphore::SmokeSignals;
2 3     3   2904 use strict;
  3         4  
  3         118  
3              
4 3     3   13 use vars qw< $VERSION @EXPORT_OK >;
  3         3  
  3         289  
5             BEGIN {
6 3     3   5 $VERSION = 0.001_002;
7 3         7 @EXPORT_OK = qw< LightUp JoinUp MeetUp >;
8 3         1571 require IO::Handle;
9 3         18066 require Exporter;
10 3         54 *import = \&Exporter::import;
11 3 50       6 if( eval { require bytes; 1 } ) {
  3         1547  
  3         30  
12 3         15 bytes->import();
13             }
14             }
15 3     3   778 use Time::HiRes qw< sleep >;
  3         1433  
  3         20  
16 3     3   1578 use Errno qw< EAGAIN EWOULDBLOCK >;
  3         2246  
  3         407  
17 3         5938 use Fcntl qw<
18             O_WRONLY O_RDONLY O_NONBLOCK
19             LOCK_EX LOCK_NB LOCK_UN
20 3     3   21 >;
  3         3  
21              
22 23     23   77 sub _SMOKE { 0 } # End to pull from.
23 13125     13125   11079 sub _STOKE { 1 } # The lit end.
24 13136     13136   119676 sub _BYTES { 2 } # Tokin' length.
25 20     20   46 sub _PUFFS { 3 } # How many tokins; how many tokers at once (if I lit it).
26 6     6   21 sub _OWNER { 4 } # PID of process that created this pipe.
27              
28              
29             sub LightUp { # Set up a new pipe.
30 2     2 1 13 return __PACKAGE__->Ignite( @_ );
31             }
32              
33             sub JoinUp { # Just use an existing pipe.
34 0     0 1 0 return __PACKAGE__->JoinIn( @_ );
35             }
36              
37             sub MeetUp { # When you are not sure who should light the pipe.
38 2     2 1 24 return __PACKAGE__->Meet( @_ );
39             }
40              
41              
42             sub _New {
43 4     4   9 my( $class, $bytes, $path, $perm, $nowait ) = @_;
44              
45 4         23 my $smoke = IO::Handle->new();
46 4         89 my $stoke = IO::Handle->new();
47 4 100       42 if( ! $path ) {
48 2 50       35 pipe( $smoke, $stoke )
49             or _croak( "Can't ignite pipe: $!\n" );
50             } else {
51 2 50 33     156 if( $perm && ! -e $path ) {
52 2 50       20 warn "WARNING: Having to create FIFO: $path\n"
53             if $nowait;
54 2         1232 require POSIX;
55 2         10376 POSIX->import('mkfifo'); # In case import() says 'unsupported'.
56 2 50       1916 mkfifo( $path, $perm )
57             or _croak( "Can't create FIFO ($path): $!\n" );
58             }
59 2 50 33     14 my $extra = $perm || $nowait ? O_NONBLOCK() : 0;
60 2 50       72 sysopen $smoke, $path, O_RDONLY()|$extra, $perm
61             or _croak( "Can't read pipe path ($path): $!\n" );
62 2 50       12 _croak( "Path ($path) is not a FIFO (named pipe)\n" )
63             if ! -p $smoke;
64 2 50       30 sysopen $stoke, $path, O_WRONLY()
65             or _croak( "Can't write pipe path ($path): $!\n" );
66             }
67 4         9 binmode $smoke;
68 4         4 binmode $stoke;
69              
70 4   33     24 my $me = bless [], ref $class || $class;
71 4         103 $me->[_SMOKE] = $smoke;
72 4         11 $me->[_STOKE] = $stoke;
73 4         10 $me->[_BYTES] = $bytes;
74              
75 4         9 return $me;
76             }
77              
78              
79             sub JoinIn { # Use an already set-up pipe.
80 0     0 1 0 my( $class, $bytes, $path ) = @_;
81 0         0 my $me = $class->_New( $bytes, $path, 0 );
82 0         0 return $me;
83             }
84              
85              
86             sub Ignite { # Set up a new pipe.
87 2     2 1 2 my( $class, $fuel, $path, $perm ) = @_;
88 2 50 0     4 $perm ||= 0666
89             if $path;
90              
91 2         3 ( $fuel, my $bytes ) = $class->_PickTheMix( $fuel );
92              
93 2         5 my $me = $class->_New( $bytes, $path, $perm );
94              
95 2         4 $me->_Roll( $fuel );
96              
97 1         4 return $me;
98             }
99              
100              
101             sub _PickTheMix {
102 4     4   6 my( $class, $fuel ) = @_;
103 4   100     15 $fuel ||= 1;
104 4         134 my $bytes;
105 4 50       13 if( ref $fuel ) {
106 0 0       0 _croak( "You brought nothing to smoke!\n" )
107             if ! @$fuel;
108 0         0 $bytes = length $fuel->[0];
109             } else {
110 4 50       25 _croak( "Specify what to smoke or how much, not '$fuel'.\n" )
111             if $fuel !~ /^[1-9][0-9]*$/;
112 4         6 $bytes = length $fuel;
113             }
114 4         10 return( $fuel, $bytes );
115             }
116              
117              
118             sub Meet { # When you are not sure who should light the pipe.
119 2     2 1 4 my( $class, $fuel, $path, $perm ) = @_;
120              
121 2         8 ( $fuel, my $bytes ) = $class->_PickTheMix( $fuel );
122              
123 2         10 my $me = $class->_New( $bytes, $path, $perm, 'nowait' );
124              
125             # See if somebody already lit the pipe:
126 2 50       6 if( flock( $me->[_SMOKE], LOCK_EX() | LOCK_NB() ) ) {
127 2         12 my $puff = $me->_Bogart('impatient');
128 2 50       6 if( defined $puff ) {
129             # Already lit, so return the magic smoke:
130 0         0 $me->_Stoke( $puff );
131             } else {
132             # I got here first! Light it up!
133 2         6 $me->_Roll( $fuel );
134             }
135 2         4 flock( $me->[_SMOKE], LOCK_UN() );
136             }
137              
138 2         26 return $me;
139             }
140              
141              
142             sub _Roll { # Put the fuel in.
143 4     4   8 my( $me, $fuel ) = @_;
144 4         11 $me->[_OWNER] = $$;
145              
146 4         8 my $stoke = $me->[_STOKE];
147 4         14 $stoke->blocking( 0 );
148 4 50       8 if( ! ref $fuel ) {
149 4         11 $me->[_PUFFS] = 0 + $fuel;
150 4         11 my $start = '0' x length $fuel;
151 4         21 $start =~ s/0$/1/;
152 4         25 for my $puff ( "$start" .. "$fuel" ) {
153 13110         13186 $me->_Stoke( $puff );
154             }
155             } else {
156 0         0 $me->[_PUFFS] = 0 + @$fuel;
157 0         0 for my $puff ( @$fuel ) {
158 0         0 $me->_Stoke( $puff );
159 0 0       0 _croak( "You can't use a string of null bytes as a tokin'" )
160             if $puff !~ /[^\0]/;
161             }
162             }
163 3         13 $stoke->blocking( 1 );
164             }
165              
166              
167             sub _MagicDragon { # Every magic dragon needs a good name.
168 9     9   42 return __PACKAGE__ . '::Puff';
169             }
170              
171              
172             sub Puff { # Get a magic dragon so you won't forget to share.
173 10     10 1 216 my( $me, $impatient ) = @_;
174 10 100       25 if( ref $me->[_PUFFS] ) {
175             return
176 1 50       2 if wantarray;
177 1         1 _croak( "The pipe is going out.\n" );
178             }
179 9         26 return $me->_MagicDragon()->_Inhale( $me, $impatient );
180             }
181              
182              
183             sub _Bogart { # Take a drag (skipping proper protocol).
184 14     14   19 my( $me, $impatient, $nil ) = @_;
185 14         28 my( $smoke ) = $me->[_SMOKE];
186 14 100       62 $smoke->blocking( 0 )
187             if $impatient;
188 14         13 my $puff;
189 14         41 my $got_none = ! sysread( $smoke, $puff, $me->[_BYTES] );
190 14         105 my $excuse = $!;
191 14 100       43 $smoke->blocking( 1 )
192             if $impatient;
193             return undef
194 14 50 100     83 if $impatient
      33        
      66        
195             && $got_none
196             && ( EAGAIN() == $excuse
197             || EWOULDBLOCK() == $excuse )
198             ;
199 9 50       19 _croak( "Can't toke pipe: $!\n" )
200             if $got_none;
201 9 50 66     81 if( ! $nil && $puff !~ /[^\0]/ ) { # Pipe is being smothered.
202 0         0 $me->_Stoke( $puff );
203 0         0 $me->_Snuff(); # Stop us from using it.
204             return
205 0 0       0 if wantarray;
206 0         0 _croak( "The pipe is going out.\n" );
207             }
208 9         46 return $puff;
209             }
210              
211              
212             sub _Stoke { # Return some magic smoke (skipping proper protocol).
213 13116     13116   10451 my( $me, $puff ) = @_;
214 13116         13182 my $stoke = $me->[_STOKE];
215 13116         12464 my $bytes = $me->[_BYTES];
216 13116 50       16048 if( $bytes != length $puff ) {
217 0         0 _croak( "Tokin' ($puff) is ", length($puff), " bytes, not $bytes!" );
218             }
219 13116 100       30519 syswrite( $stoke, $puff )
220             or die "Can't stoke pipe (with '$puff'): $!\n";
221             }
222              
223              
224             # Returns undef if we aren't allowed to extinguish it.
225             # Returns 0 if pipe is now completely extinguished.
226             # Otherwise, returns number of outstanding tokins remaining.
227              
228             sub Extinguish { # Last call!
229 2     2 1 36 my( $me, $impatient ) = @_;
230 2         3 my $puffs = $me->[_PUFFS];
231              
232 2         2 my $left = undef; # Returned if we didn't start the fire.
233 2 50 50     9 $left = 0 # Returned when it is all the way out...
      33        
234             if defined $puffs # ...since we did start the fire.
235             && $$ == ( $me->[_OWNER] || 0 );
236              
237 2 50 66     8 return $left # We already put out at least ours.
238             if ref $puffs && ! @$puffs;
239              
240 2 50       3 if( defined $left ) { # We brought it up so we can take it down:
241 2         3 $left = $me->_Smother( $impatient );
242 2 100       8 return $left # Not all the way out yet.
243             if $left;
244             }
245             # Either all the way out or just extinguishing our access to it:
246 1         7 $me->_Snuff();
247 1         3 return $left;
248             }
249              
250              
251             sub _Snuff {
252 1     1   1 my( $me ) = @_;
253 1         2 for my $puffs ( $me->[_PUFFS] ) {
254             return
255 1 50 33     7 if ref $puffs && ! @$puffs;
256 1         21 $puffs = [];
257             }
258 1         2 close $me->[_STOKE];
259 1         2 close $me->[_SMOKE];
260             }
261              
262              
263             sub _Smother {
264 2     2   1 my( $me, $impatient ) = @_;
265 2         3 my $puffs = $me->[_PUFFS];
266 2         6 my $eop = "\0" x $me->[_BYTES]; # The End-Of-Pipe tokin'.
267 2         1 my $eops; # How many EOPs in pipe?
268             my $room; # How much room in pipe for EOP tokins?
269 2 100       5 if( ! ref $puffs ) { # Our first try at shutting down:
270 1         1 $room = $eops = 0; # Nothing drained, no EOPs sent.
271             } else {
272 1         1 ( $puffs, $room, $eops ) = @$puffs;
273             }
274              
275 2         1 my $left;
276 2         2 my $loops = 0;
277 2         4 while( 0 < ( $left = $puffs + $eops ) ) {
278 3   100     9 my $puff = $me->_Bogart(
279             $impatient || ! $eops, # Don't wait before injecting EOP.
280             'nil' );
281 3 100       9 if( ! defined $puff ) { # Pipe empty:
    100          
282 1 50 33     3 if( ! $room && ! $eops ) { # "No room" but pipe empty:
283 1         4 $me->_Stoke( $eop ); ++$eops; # Risk just 1 EOP.
  1         1  
284             }
285             } elsif( $puff =~ /[^\0]/ ) { # We eliminated another non-EOP tokin':
286 1         1 --$puffs;
287 1 50       2 ++$room if $room < 2;
288 1         1 $loops = 0; # Don't sleep while reaping tokins.
289             } else {
290 1         1 --$eops; # Got an EOP back.
291             }
292             last # All puffed out!
293 3 50 66     10 if ! $puffs && ! $eops;
294             # Don't inject EOPs if would just cause $impatient to never return:
295 2 50 66     8 if( ! $impatient # If patient, then we might loop w/ sleep.
      33        
296             || ! defined $puff # We are about to return, so inject.
297             || $puff =~ /[^\0]/ # We got a non-EOP tokin' so add more EOP.
298             ) {
299 2   33     8 while( $puffs && $eops < $room ) {
300 0         0 $me->_Stoke( $eop ); ++$eops;
  0         0  
301             }
302             }
303 2 100 66     5 if( $impatient && ! defined $puff ) { # We had emptied the pipe:
304 1         2 $me->[_PUFFS] = [$puffs,$room,$eops];
305 1         4 return $left; # Report: Others need time.
306             }
307 1 50       3 sleep( 0.1 ) # Don't do a tight CPU loop.
308             if 2 < ++$loops;
309             }
310 1         1 return 0;
311             }
312              
313              
314             sub _croak {
315 1     1   5 require Carp;
316 1         118 Carp::croak( @_ );
317             }
318              
319              
320             our @CARP_NOT;
321              
322             package IPC::Semaphore::SmokeSignals::Puff;
323             push @CARP_NOT, __PACKAGE__;
324              
325             sub _Inhale {
326 9     9   12 my( $class, $pipe, $impatient ) = @_;
327 9 50       21 my( $puff ) = $pipe->_Bogart($impatient)
328             or return;
329 9 100       27 $puff or return undef;
330 7         57 return bless [ $pipe, $puff ], $class;
331             }
332              
333             sub Sniff {
334 4     4   683 my( $me ) = @_;
335 4         28 return $me->[1];
336             }
337              
338             sub Exhale {
339 6     6   33 my( $me ) = @_;
340             return
341 6 100       45 if ! @$me;
342 5         18 my( $pipe, $puff ) = splice @$me;
343 5         10 $pipe->_Stoke( $puff );
344             }
345              
346             sub DESTROY {
347 5     5   92 my( $me ) = @_;
348 5         14 $me->Exhale();
349             }
350              
351              
352             1;
353             __END__