File Coverage

blib/lib/IPC/Notify.pm
Criterion Covered Total %
statement 21 136 15.4
branch 0 62 0.0
condition 0 12 0.0
subroutine 7 20 35.0
pod 6 6 100.0
total 34 236 14.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             # Copyright 2007 Jason Long. All rights reserved.
4              
5             package IPC::Notify;
6 1     1   21785 use strict;
  1         3  
  1         37  
7 1     1   5 use warnings;
  1         3  
  1         27  
8 1     1   4 use Carp;
  1         6  
  1         95  
9 1     1   5 use Fcntl ":flock", ":seek";
  1         2  
  1         163  
10 1     1   1155 use POSIX "mkfifo";
  1         8986  
  1         6  
11 1     1   2088654 use File::Temp "tempdir";
  1         36433  
  1         182  
12             our $VERSION = 0.002;
13              
14             =head1 NAME
15              
16             IPC::Notify
17              
18             =head1 SYNOPSIS
19              
20             # Process 1- waits to be notified and then performs work
21             my $notify = IPC::Notify->new("/path/to/lock");
22             $notify->lock;
23             for (;;) {
24             $notify->wait;
25             # do work
26             print "I am doing some work!\n";
27             }
28             $notify->unlock;
29              
30             # Process 2- wakes up process 1
31             my $notify = IPC::Notify->new("/path/to/lock");
32             $notify->lock;
33             $notify->notify;
34             $notify->unlock;
35              
36             =head1 CONSTRUCTOR
37              
38             =head2 new() - create a new notify locking object
39              
40             my $notify = IPC::Notify->new($filename);
41              
42             =cut
43              
44             sub new
45             {
46 0     0 1   my $class = shift;
47 0           my ($file) = @_;
48 0           my $self = bless { file => $file }, $class;
49              
50             # create the file if necessary
51 0 0         open my $fh, ">>", $file
52             or die "Error: cannot create $file: $!\n";
53 0           close $fh;
54              
55 0           return $self;
56             }
57              
58             =head1 METHODS
59              
60             =cut
61              
62             #returns 1 if a byte was read from the fifo
63             #returns undef if timeout occurred
64             sub _read_from_fifo
65             {
66 0     0     my $self = shift;
67 0           my ($fifofh, $timeout) = @_;
68              
69             #print STDERR "selecting...\n";
70 1     1   843 use IO::Select;
  1         1390  
  1         1208  
71 0           my $s = IO::Select->new;
72 0           $s->add($fifofh);
73              
74 0           my @ready = $s->can_read($timeout);
75 0 0         if (@ready)
76             {
77             #print STDERR "reading...\n";
78 0           my $buf = "";
79 0           read $fifofh, $buf, 1;
80 0           return 1;
81             }
82 0           return;
83             }
84              
85             sub _close_fifo
86             {
87 0     0     my $self = shift;
88 0 0         return unless $self->{fifofh};
89 0           close $self->{fifofh};
90 0           $self->{fifofh} = undef;
91             }
92              
93             sub _create_fifo
94             {
95 0     0     my $self = shift;
96 0 0         unless ($self->{fifofile})
97             {
98 0           my $tempdir = tempdir(CLEANUP => 1);
99 0   0       my $fifo_name = $self->{fifo_name} || "$$.fifo";
100 0           my $fifofile = "$tempdir/$fifo_name";
101 0 0         mkfifo($fifofile, 0777)
102             or die "Error: cannot mkfifo: $!\n";
103 0           $self->{fifofile} = $fifofile;
104             }
105 0 0         unless ($self->{fifofh})
106             {
107 0           my $fifofile = $self->{fifofile};
108             #print STDERR "opening $fifofile...\n";
109 0 0         open my $fifofh, "+<", $fifofile
110             or die "Error: cannot open $fifofile: $!\n";
111 0           $self->{fifofh} = $fifofh;
112             }
113 0           return $self->{fifofh};
114             }
115              
116             sub _debug
117             {
118 0     0     my $self = shift;
119 0           print STDERR "===begin " . $self->{file} . "===\n";
120 0           my $fh = $self->{fh};
121 0 0         seek $fh, 0, SEEK_SET
122             or die "Error: cannot seek: $!\n";
123 0           while (<$fh>)
124             {
125 0           print STDERR $_;
126             }
127 0           print STDERR "===end " . $self->{file} . "===\n";
128             }
129              
130             sub _put_hash_at
131             {
132 0     0     my $self = shift;
133 0           my ($pos) = @_;
134              
135 0           my $fh = $self->{fh};
136 0 0         seek $fh, $pos, SEEK_SET
137             or die "Error: cannot seek: $!\n";
138 0           print $fh "#";
139             }
140              
141             sub _put_line
142             {
143 0     0     my $self = shift;
144 0           my ($to_write) = @_;
145              
146 0           $to_write .= "\n";
147 0           my $need_len = length($to_write);
148              
149 0           my $fh = $self->{fh};
150 0 0         seek $fh, 0, SEEK_SET
151             or die "Error: can't seek: $!\n";
152 0           my $found_pos;
153 0           my $found_len = 0;
154 0           for (;;)
155             {
156 0           my $cur_pos = tell $fh;
157 0           my $line = <$fh>;
158 0 0         last unless defined $line;
159 0 0 0       if ($line =~ /^#/ or $line =~ /^$/)
160             {
161 0           my $len = length($line);
162 0 0         unless (defined $found_pos)
163             {
164 0           $found_pos = $cur_pos;
165 0           $found_len = 0;
166             }
167 0           $found_len += $len;
168 0 0         last if $found_len >= $need_len;
169             }
170             else
171             {
172 0           undef $found_pos;
173             }
174             }
175              
176 0 0         if (defined $found_pos)
177             {
178             # print STDERR "need $need_len bytes\n";
179             # print STDERR "found " . $found_len . " bytes at pos="
180             # . $found_pos . "\n";
181 0 0         seek $fh, $found_pos, SEEK_SET
182             or die "Error: cannot seek: $!\n";
183 0 0         $to_write .= "#" if $found_len > ($need_len + 1);
184             }
185             else
186             {
187 0 0         seek $fh, 0, SEEK_END
188             or die "Error: cannot seek to end: $!\n";
189 0           $found_pos = tell $fh;
190             #print STDERR "end=$found_pos\n";
191             }
192 0           print $fh $to_write;
193              
194 0           return $found_pos;
195             }
196              
197             sub _write_to_fifo
198             {
199 0     0     my $self = shift;
200 0           my ($fifofile) = @_;
201             #print STDERR "opening $fifofile...\n";
202 0 0         open my $fifofh, "+>", $fifofile
203             or die "Error: cannot write to $fifofile: $!\n";
204 0           print STDERR "writing to $fifofile...\n";
205 0           print $fifofh ".";
206             #print STDERR "closing $fifofile...\n";
207 0           close $fifofh;
208             }
209              
210             =head2 is_locked() - check whether object is currently "locked"
211              
212             if ($notify->is_locked) { ... }
213              
214             Returns nonzero if the object is currently locked.
215              
216             =cut
217              
218             sub is_locked
219             {
220 0     0 1   my $self = shift;
221 0           return 0 < $self->{lock_count};
222             }
223              
224             =head2 lock() - obtain a file lock
225              
226             $notify->lock;
227              
228             A lock must be acquired before using wait() or notify() on this object.
229             This ensures proper synchronization. This method will block if another
230             (non-waiting) process has the lock.
231              
232             =cut
233              
234             sub lock
235             {
236 0     0 1   my $self = shift;
237 0 0         return if 0 < $self->{lock_count}++;
238              
239 0           my $file = $self->{file};
240 0 0         open my $fh, "+<", $file
241             or die "Error: cannot open $file: $!\n";
242 0 0         flock $fh, LOCK_EX
243             or die "Error: cannot lock $file: $!\n";
244 0           $self->{fh} = $fh;
245             }
246              
247             =head2 notify() - wake up all processes waiting on this lock
248              
249             $notify->notify;
250              
251             This will wake up all processes waiting on the lock, however,
252             you need to call unlock() from the notifying process before
253             the other process(es) will be allowed to proceed.
254              
255             =cut
256              
257             sub notify
258             {
259 0     0 1   my $self = shift;
260 0 0         croak "not locked" unless $self->is_locked;
261              
262 0 0         return if $self->{notified};
263              
264 0           my $fh = $self->{fh};
265 0 0         seek $fh, 0, SEEK_SET
266             or die "Error: cannot seek: $!\n";
267 0           while (<$fh>)
268             {
269 0           chomp;
270 0 0 0       next if (/^\s*#/ || /^\s*$/);
271 0           $self->_write_to_fifo($_);
272             }
273 0           $self->{notified} = 1;
274             }
275              
276             =head2 wait() - wait for a notification on this lock
277              
278             $notify->wait($timeout_in_seconds);
279              
280             This method will atomically give up the lock this process has
281             on the object and wait for a notification. Before returning
282             control, it will re-acquire the lock.
283              
284             If $timeout_in_seconds is specified, wait() will return control
285             early if a notification is not received within
286             the specified time. Fractional values are acceptable.
287              
288             If $timeout_in_seconds is absent, or "undef", then it
289             will wait forever. If $timeout_in_seconds is zero,
290             the call will be nonblocking. (It will simply indicate whether
291             a notification has been received.)
292              
293             The result is nonzero if a notification was received.
294             Otherwise, the timeout had elapsed.
295              
296             =cut
297              
298             sub wait
299             {
300 0     0 1   my $self = shift;
301 0           my $timeout = shift;
302 0 0         croak "not locked" unless $self->is_locked;
303              
304             # create a fifo
305 0           my $fifofh = $self->_create_fifo;
306              
307             # write the name of this fifo to our lock file
308 0           my $pos = $self->_put_line($self->{fifofile});
309              
310             # release the file lock
311 0 0         flock $self->{fh}, LOCK_UN
312             or die "Error: cannot unlock: $!\n";
313              
314             #sleep 5;
315              
316             # read from fifo
317 0           my $result = $self->_read_from_fifo($fifofh, $timeout);
318              
319             #print STDERR "result=$result\n";
320             #sleep 5;
321              
322             #$self->_debug;
323              
324 0 0         flock $self->{fh}, LOCK_EX
325             or die "Error: cannot lock: $!\n";
326              
327             # try again to read from the fifo, this time with timeout=0
328 0   0       $result ||= $self->_read_from_fifo($fifofh, 0);
329              
330             # no longer need it in the lock file
331 0 0         unless ($ENV{LEAVEIT}) {
332 0           $self->_put_hash_at($pos);
333             }
334              
335 0           $self->{notified} = undef;
336 0           return $result;
337             }
338              
339             =head2 unlock() - release a lock
340              
341             $notify->unlock;
342              
343             Be sure to unlock() if you are going to do some other work.
344             As long as one process holds the lock, other processes will block
345             to notify().
346              
347             =cut
348              
349             sub unlock
350             {
351 0     0 1   my $self = shift;
352 0 0         return if 0 < --$self->{lock_count};
353              
354 0           $self->_close_fifo;
355              
356 0           my $fh = $self->{fh};
357 0           flock $fh, LOCK_UN;
358 0           close $fh;
359              
360 0           $self->{notified} = undef;
361             }
362              
363             1;