File Coverage

blib/lib/IPC/Locker/Server.pm
Criterion Covered Total %
statement 27 348 7.7
branch 0 230 0.0
condition 0 93 0.0
subroutine 9 35 25.7
pod 2 23 8.7
total 38 729 5.2


line stmt bran cond sub pod time code
1             # See copyright, etc in below POD section.
2             ######################################################################
3              
4             =head1 NAME
5              
6             IPC::Locker::Server - Distributed lock handler server
7              
8             =head1 SYNOPSIS
9              
10             use IPC::Locker::Server;
11              
12             IPC::Locker::Server->new(port=>1234)->start_server;
13              
14             # Or more typically via the command line
15             lockerd
16              
17             =head1 DESCRIPTION
18              
19             L provides the server for the IPC::Locker package.
20              
21             =over 4
22              
23             =item new ([parameter=>value ...]);
24              
25             Creates a server object.
26              
27             =item start_server ([parameter=>value ...]);
28              
29             Starts the server. Does not return.
30              
31             =back
32              
33             =head1 PARAMETERS
34              
35             =over 4
36              
37             =item family
38              
39             The family of transport to use, either INET or UNIX. Defaults to INET.
40              
41             =item port
42              
43             The port number (INET) or name (UNIX) of the lock server. Defaults to
44             'lockerd' looked up via /etc/services, else 1751.
45              
46             =back
47              
48             =head1 DISTRIBUTION
49              
50             The latest version is available from CPAN and from L.
51              
52             Copyright 1999-2017 by Wilson Snyder. This package is free software; you
53             can redistribute it and/or modify it under the terms of either the GNU
54             Lesser General Public License Version 3 or the Perl Artistic License Version 2.0.
55              
56             =head1 AUTHORS
57              
58             Wilson Snyder
59              
60             =head1 SEE ALSO
61              
62             L, L
63              
64             =cut
65              
66             ######################################################################
67              
68             package IPC::Locker::Server;
69             require 5.006;
70             require Exporter;
71             @ISA = qw(Exporter);
72              
73 2     2   34923 use IPC::Locker;
  2         3  
  2         86  
74 2     2   14 use Socket;
  2         3  
  2         731  
75 2     2   11 use IO::Socket;
  2         19  
  2         10  
76 2     2   1999 use IO::Poll qw(POLLIN POLLOUT POLLERR POLLHUP POLLNVAL);
  2         1202  
  2         112  
77 2     2   11 use Time::HiRes;
  2         3  
  2         13  
78              
79 2     2   118 use IPC::PidStat;
  2         4  
  2         49  
80 2     2   8 use strict;
  2         3  
  2         37  
81 2     2   7 use vars qw($VERSION $Debug %Locks %Clients $Poll $Interrupts $Hostname $Exister);
  2         4  
  2         118  
82 2     2   9 use Carp;
  2         3  
  2         6718  
83              
84             ######################################################################
85             #### Configuration Section
86              
87             # Other configurable settings.
88             $Debug = 0;
89              
90             $VERSION = '1.496';
91             $Hostname = IPC::Locker::hostfqdn();
92              
93             ######################################################################
94             #### Globals
95              
96             # All held locks
97             %Locks = ();
98             our $_Client_Num = 0; # Debug use only
99             our $StartTime = time();
100              
101             our $RecheckLockDelta = 1; # Loop all locks every N seconds
102             our $PollDelta = 1; # Poll every N seconds for activity
103             our $AutoUnlockCheckDelta = 2; # Check every N seconds for pid existance
104             our $AutoUnlockCheckPerSec = 100; # Check at most N existances per second
105              
106             ######################################################################
107             #### Creator
108              
109             sub new {
110             # Establish the server
111 0 0   0 1   @_ >= 1 or croak 'usage: IPC::Locker::Server->new ({options})';
112 0           my $proto = shift;
113 0   0       my $class = ref($proto) || $proto;
114 0           my $self = {
115             #Documented
116             port=>$IPC::Locker::Default_Port,
117             family=>$IPC::Locker::Default_Family,
118             host=>'localhost',
119             @_,};
120 0           bless $self, $class;
121 0           my $param = {@_};
122 0 0 0       if (defined $param->{family} && $param->{family} eq 'UNIX'
      0        
123             && !exists($param->{port})) {
124 0           $self->{port} = $IPC::Locker::Default_UNIX_port;
125             }
126 0           return $self;
127             }
128              
129             sub start_server {
130 0     0 1   my $self = shift;
131              
132             # Open the socket
133 0 0         _timelog("Listening on $self->{port}\n") if $Debug;
134 0           my $server;
135 0 0         if ($self->{family} eq 'INET') {
    0          
136             $server = IO::Socket::INET->new( Proto => 'tcp',
137             LocalAddr => $self->{host},
138             LocalPort => $self->{port},
139 0 0         Listen => SOMAXCONN,
140             Reuse => 1)
141             or die "$0: Error, socket: $!";
142             } elsif ($self->{family} eq 'UNIX') {
143             $server = IO::Socket::UNIX->new(Local => $self->{port},
144 0 0         Listen => SOMAXCONN,
145             Reuse => 1)
146             or die "$0: Error, socket: $!\n port=$self->{port}=";
147 0           $self->{unix_socket_created}=1;
148             } else {
149 0           die "IPC::Locker::Server: What transport do you want to use?";
150             }
151 0           $Poll = IO::Poll->new();
152 0           $Poll->mask($server => (POLLIN | POLLERR | POLLHUP | POLLNVAL));
153              
154 0           $Exister = IPC::PidStat->new();
155 0           my $exister_fh = $Exister->fh; # Avoid method calls, to accelerate things
156 0           $Poll->mask($exister_fh => (POLLIN | POLLERR | POLLHUP | POLLNVAL));
157              
158 0           %Clients = ();
159             #$SIG{ALRM} = \&sig_alarm;
160 0           $SIG{INT}= \&sig_INT;
161 0           $SIG{HUP}= \&sig_INT;
162              
163 0           $! = 0;
164 0           while (!$Interrupts) {
165 0 0         _timelog("Pre-poll $!\n") if $Debug;
166             #use Data::Dumper; Carp::cluck(Dumper(\%Clients, \%Locks));
167 0           $! = 0;
168 0           my (@r, @w, @e);
169              
170 0 0         my $timeout = ((scalar keys %Locks) ? $PollDelta : 2000);
171 0           my $npolled = $Poll->poll($timeout);
172 0 0         if ($npolled>0) {
173 0           @r = $Poll->handles(POLLIN);
174 0           @e = $Poll->handles(POLLERR | POLLHUP | POLLNVAL);
175             #@w = $Poll->handles(POLLOUT);
176             }
177 0 0         _timelog("Poll $npolled Locks=",(scalar keys %Locks),": $#r $#w $#e $!\n") if $Debug;
178 0           foreach my $fh (@r) {
179 0 0         if ($fh == $server) {
    0          
180             # Create a new socket
181 0           my $clientfh = $server->accept;
182 0           $Poll->mask($clientfh => (POLLIN | POLLERR | POLLHUP | POLLNVAL));
183             #
184 0           my $clientvar = {socket=>$clientfh,
185             input=>'',
186             inputlines=>[],
187             };
188 0 0         $clientvar->{client_num} = $_Client_Num++ if $Debug;
189 0           $Clients{$clientfh}=$clientvar;
190 0 0         client_send($clientvar,"HELLO\n") if $Debug;
191             } elsif ($fh == $exister_fh) {
192 0           exist_traffic();
193             } else {
194 0           my $data = '';
195             # For debug, change the 1000 to 1 below
196 0           my $rc = recv($fh, $data, 1000, 0);
197 0 0         if ($data eq '') {
198             # we have finished with the socket
199 0           delete $Clients{$fh};
200 0           $Poll->remove($fh);
201 0           $fh->close;
202             } else {
203 0           my $line = $Clients{$fh}->{input}.$data;
204 0           my @lines = split /\n/, $line;
205 0 0         if ($line =~ /\n$/) {
206 0           $Clients{$fh}->{input}='';
207 0 0         _timelog("Nothing Left\n") if $Debug;
208             } else {
209 0           $Clients{$fh}->{input}=pop @lines;
210 0 0         _timelog("Left: ".$Clients{$fh}->{input}."\n") if $Debug;
211             }
212 0           client_service($Clients{$fh}, \@lines);
213             }
214             }
215             }
216 0           foreach my $fh (@e) {
217             # we have finished with the socket
218 0           delete $Clients{$fh};
219 0           $Poll->remove($fh);
220 0           $fh->close;
221             }
222 0           $self->recheck_locks();
223             }
224 0 0         _timelog("Loop end\n") if $Debug;
225             }
226              
227             ######################################################################
228             ######################################################################
229             #### Client servicing
230              
231             sub client_service {
232 0   0 0 0   my $clientvar = shift || die;
233 0           my $linesref = shift;
234             # Loop getting commands from a specific client
235 0 0         _timelog("c$clientvar->{client_num}: REQS $clientvar->{socket}\n") if $Debug;
236              
237 0 0         if (defined $clientvar->{inputlines}[0]) {
238 0 0         _timelog("c$clientvar->{client_num}: handling pre-saved lines\n") if $Debug;
239 0           $linesref = [@{$clientvar->{inputlines}}, @{$linesref}];
  0            
  0            
240 0           $clientvar->{inputlines} = []; # Zap, in case we get called recursively
241             }
242              
243             # We may return before processing all lines, thus the lines are
244             # stored in the client variables
245 0           while (defined (my $line = shift @{$linesref})) {
  0            
246 0 0         _timelog("c$clientvar->{client_num}: REQ $line\n") if $Debug;
247 0           my ($cmd,@param) = split /\s+/, $line; # We rely on the newline to terminate the split
248 0 0         if ($cmd) {
249             # Variables
250 0 0         if ($cmd eq 'user') { $clientvar->{user} = $param[0]; }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
251 0           elsif ($cmd eq 'locks') { $clientvar->{locks} = [@param]; }
252 0           elsif ($cmd eq 'block') { $clientvar->{block} = $param[0]; }
253 0           elsif ($cmd eq 'timeout') { $clientvar->{timeout} = $param[0]; }
254 0           elsif ($cmd eq 'autounlock') { $clientvar->{autounlock} = $param[0]; }
255 0           elsif ($cmd eq 'hostname') { $clientvar->{hostname} = $param[0]; }
256 0           elsif ($cmd eq 'pid') { $clientvar->{pid} = $param[0]; }
257              
258             # Frequent Commands
259             elsif ($cmd eq 'UNLOCK') {
260 0           client_unlock ($clientvar);
261             }
262             elsif ($cmd eq 'LOCK') {
263 0           my $wait = client_lock ($clientvar);
264 0 0         _timelog("c$clientvar->{client_num}: Wait= $wait\n") if $Debug;
265 0 0         last if $wait;
266             }
267             elsif ($cmd eq 'EOF') {
268 0           client_close ($clientvar);
269 0           undef $clientvar;
270 0           last;
271             }
272              
273             # Infrequent commands
274             elsif ($cmd eq 'STATUS') {
275 0           client_status ($clientvar);
276             }
277             elsif ($cmd eq 'BREAK_LOCK') {
278 0           client_break ($clientvar);
279             }
280             elsif ($cmd eq 'DEAD_PID') {
281 0           dead_pid($param[0],$param[1]);
282             }
283             elsif ($cmd eq 'LOCK_LIST') {
284 0           client_lock_list ($clientvar);
285             }
286             elsif ($cmd eq 'VERSION') {
287 0           client_send ($clientvar, "version $VERSION $StartTime\n\n");
288             }
289             elsif ($cmd eq 'RESTART') {
290 0           die "restart";
291             }
292             }
293             # Commands
294             }
295              
296             # Save any non-processed lines (from 'last') for next time
297 0           $clientvar->{inputlines} = $linesref;
298             }
299              
300             sub client_close {
301 0   0 0 0   my $clientvar = shift || die;
302 0 0         if ($clientvar->{socket}) {
303 0           delete $Clients{$clientvar->{socket}};
304 0           $Poll->remove($clientvar->{socket});
305 0           $clientvar->{socket}->close();
306             }
307 0           $clientvar->{socket} = undef;
308             }
309              
310             sub client_status {
311             # Send status of lock back to client
312             # Return 1 if success (client didn't hangup)
313 0   0 0 0   my $clientvar = shift || die;
314 0           $clientvar->{locked} = 0;
315 0           $clientvar->{owner} = "";
316 0           my $send = "";
317 0           foreach my $lockname (@{$clientvar->{locks}}) {
  0            
318 0 0         if (my $locki = locki_find ($lockname)) {
319 0 0         if ($locki->{owner} eq $clientvar->{user}) { # (Re) got lock
320 0           $clientvar->{locked} = 1;
321 0           $clientvar->{locks} = [$locki->{lock}];
322 0           $clientvar->{owner} = $locki->{owner}; # == Ourself
323 0 0         if ($clientvar->{told_locked}) {
324 0           $clientvar->{told_locked} = 0;
325 0           $send .= "print_obtained\n";
326             }
327 0           last;
328             } else {
329             # Indicate first owner, for client "waiting" message
330 0 0         $clientvar->{owner} = $locki->{owner} if !$clientvar->{owner};
331             }
332             }
333             }
334              
335 0           $send .= "owner $clientvar->{owner}\n";
336 0           $send .= "locked $clientvar->{locked}\n";
337 0 0         $send .= "lockname $clientvar->{locks}[0]\n" if $clientvar->{locked};
338 0 0         $send .= "error $clientvar->{error}\n" if $clientvar->{error};
339 0           $send .= "\n\n"; # End of group. Some day we may not always send EOF immediately
340 0           return client_send ($clientvar, $send);
341             }
342              
343             sub client_lock_list {
344 0   0 0 0   my $clientvar = shift || die;
345 0 0         _timelog("c$clientvar->{client_num}: Locklist!\n") if $Debug;
346 0           while (my ($lockname, $lock) = each %Locks) {
347 0 0         if (!$lock->{locked}) {
348 0 0         _timelog("c$clientvar->{client_num}: Note unlocked lock $lockname\n") if $Debug;
349 0           next;
350             }
351 0           client_send ($clientvar, "lock $lockname $lock->{owner}\n");
352             }
353 0           return client_send ($clientvar, "\n\n");
354             }
355              
356             sub client_lock {
357             # Client wants this lock, return true if delayed transaction
358 0   0 0 0   my $clientvar = shift || die;
359              
360             # Fast case, see if there are any non-allocated locks
361 0           foreach my $lockname (@{$clientvar->{locks}}) {
  0            
362 0 0         _timelog("c$clientvar->{client_num}: check $lockname\n") if $Debug;
363 0           my $locki = locki_find ($lockname);
364 0 0 0       if ($locki && $locki->{owner} ne $clientvar->{user}) {
365             # See if the user's machine can clear it
366 0 0 0       if ($locki->{autounlock} && $clientvar->{autounlock}) {
367             # The 2 is for supports DEAD_PID added in version 1.480
368             # Older clients will ignore it.
369 0           client_send ($clientvar, "autounlock_check $locki->{lock} $locki->{hostname} $locki->{pid} 2\n");
370             }
371             # Try to have timer/exister clear up existing lock
372 0           locki_recheck($locki,undef); # locki maybe deleted
373             } else {
374 0 0         if (!$clientvar->{locked}) { # Unlikely - some async path established the lock
375             # Know there's a free lock; for speed, munge request to point to only it
376 0           $clientvar->{locks} = [$lockname];
377 0           last;
378             }
379             }
380             }
381              
382             # Create lock requests
383 0           my $first_locki = undef;
384 0           foreach my $lockname (@{$clientvar->{locks}}) {
  0            
385 0 0         _timelog("c$clientvar->{client_num}: new $lockname\n") if $Debug;
386             # Create new request. If it can be serviced, this will
387             # establish the lock and send status back.
388 0           my $locki = locki_new_request($lockname, $clientvar);
389 0   0       $first_locki ||= $locki;
390             # Done if found free lock
391 0 0         last if $clientvar->{locked};
392             }
393              
394             # All locks busy?
395 0 0         if ($clientvar->{locked}) {
    0          
396             # Done, and we already sent client_status when the lock was made
397 0           return 0;
398             } elsif (!$clientvar->{block}) {
399             # All busy, and user wants non-blocking, just send status
400 0           client_status($clientvar);
401 0           return 0;
402             } else {
403             # All busy, we need to block the user's request and tell the user
404 0 0 0       if (!$clientvar->{told_locked} && $first_locki) {
405 0           $clientvar->{told_locked} = 1;
406 0           client_send ($clientvar, "print_waiting $first_locki->{owner}\n");
407             }
408             # Either need to wait for timeout, or someone else to return key
409 0           return 1; # Exit loop and check if can lock later
410             }
411             }
412              
413             sub client_break {
414 0   0 0 0   my $clientvar = shift || die;
415             # The locki may be deleted by this call
416 0           foreach my $lockname (@{$clientvar->{locks}}) {
  0            
417 0 0         if (my $locki = locki_find ($lockname)) {
418 0 0         if ($locki->{locked}) {
419 0 0         _timelog("c$clientvar->{client_num}: broke lock $locki->{locks} User $clientvar->{user}\n") if $Debug;
420 0           client_send ($clientvar, "print_broke $locki->{owner}\n");
421 0           locki_unlock ($locki); # locki may be deleted
422             }
423             }
424             }
425 0           client_status ($clientvar);
426             }
427              
428             sub client_unlock {
429 0   0 0 0   my $clientvar = shift || die;
430             # Client request to unlock the given lock
431             # The locki may be deleted by this call
432 0           $clientvar->{locked} = 0;
433 0           foreach my $lockname (@{$clientvar->{locks}}) {
  0            
434 0 0         if (my $locki = locki_find ($lockname)) {
435 0 0         if ($locki->{owner} eq $clientvar->{user}) {
436 0 0         _timelog("c$clientvar->{client_num}: Unlocked $locki->{lock} User $clientvar->{user}\n") if $Debug;
437 0           locki_unlock ($locki); # locki may be deleted
438             } else {
439             # Doesn't hold lock but might be waiting for it.
440 0 0         _timelog("c$clientvar->{client_num}: Waiter count: ".$#{$locki->{waiters}}."\n") if $Debug;
  0            
441 0           for (my $n=0; $n <= $#{$locki->{waiters}}; $n++) {
  0            
442 0 0         if ($locki->{waiters}[$n]{user} eq $clientvar->{user}) {
443 0 0         _timelog("c$clientvar->{client_num}: Dewait $locki->{lock} User $clientvar->{user}\n") if $Debug;
444 0           splice @{$locki->{waiters}}, $n, 1;
  0            
445             }
446             }
447             }
448             }
449             }
450 0           client_status ($clientvar);
451             }
452              
453             sub client_send {
454             # Send a string to the client, return 1 if success
455 0   0 0 0   my $clientvar = shift || die;
456 0           my $msg = shift;
457              
458 0           my $clientfh = $clientvar->{socket};
459 0 0         return 0 if (!$clientfh);
460 0 0         _timelog_split("c$clientvar->{client_num}: RESP $clientfh",
461             (' 'x24)."c$clientvar->{client_num}: RES ", $msg) if $Debug;
462              
463 0           $SIG{PIPE} = 'IGNORE';
464 0           my $status = eval { local $^W=0; send $clientfh,$msg,0; }; # Disable warnings
  0            
  0            
465 0 0         if (!$status) {
466 0 0 0       warn "client_send hangup $? $! ".($status||"")." $clientfh " if $Debug;
467 0           client_close ($clientvar);
468 0           return 0;
469             }
470 0           return 1;
471             }
472              
473             ######################################################################
474             ######################################################################
475             #### Alarm handler
476              
477             sub sig_INT {
478 0     0 0   $Interrupts++;
479             #$SIG{INT}= \&sig_INT;
480 0           0;
481             }
482              
483             sub alarm_time {
484             # Compute alarm interval and set
485 0     0 0   die "Dead code\n";
486 0           my $time = fractime();
487 0           my $timelimit = undef;
488 0           foreach my $locki (values %Locks) {
489 0 0 0       if ($locki->{locked} && $locki->{timelimit}) {
490             $timelimit = $locki->{timelimit} if
491             (!defined $timelimit
492 0 0 0       || $locki->{timelimit} <= $timelimit);
493             }
494             }
495 0 0         return $timelimit ? ($timelimit - $time + 1) : 0;
496             }
497              
498             sub fractime {
499 0     0 0   my ($time, $time_usec) = Time::HiRes::gettimeofday();
500 0           return $time + $time_usec * 1e-6;
501             }
502              
503             ######################################################################
504             ######################################################################
505             #### Exist traffic
506              
507             sub exist_traffic {
508             # Handle UDP responses from our $Exister->pid_request calls.
509 0 0   0 0   _timelog("UDP PidStat in...\n") if $Debug;
510 0           my ($pid,$exists,$onhost) = $Exister->recv_stat();
511 0 0 0       if (defined $pid && defined $exists && !$exists) {
      0        
512             # We only care about known-missing processes
513 0 0         _timelog(" UDP PidStat PID $pid no longer with us. RIP.\n") if $Debug;
514 0           dead_pid($onhost,$pid);
515             }
516             }
517              
518             sub dead_pid {
519 0     0 0   my $host = shift;
520 0           my $pid = shift;
521             # We don't maintain a table sorted by pid, as these messages
522             # are rare, and there can be many locks per pid.
523 0           foreach my $locki (values %Locks) {
524 0 0 0       if ($locki->{locked} && $locki->{autounlock}
      0        
      0        
525             && $locki->{hostname} eq $host
526             && $locki->{pid} == $pid) {
527 0 0         _timelog("\tUDP RIP Unlock\n") if $Debug;
528 0           locki_unlock($locki); # break the lock, locki may be deleted
529             }
530             }
531 0 0         _timelog(" UDP RIP done\n\n") if $Debug;
532             }
533              
534             ######################################################################
535             ######################################################################
536             #### Internals
537              
538             sub locki_action {
539             # Give lock to next requestor that accepts it
540 0   0 0 0   my $locki = shift || die;
541              
542 0 0         _timelog("$locki->{lock}: Locki_action:Waiter count: ".$#{$locki->{waiters}}."\n") if $Debug;
  0            
543 0 0 0       if (!$locki->{locked} && defined $locki->{waiters}[0]) {
    0 0        
544 0           my $clientvar = shift @{$locki->{waiters}};
  0            
545             # Give it to a client. If it fails, it will call locki_unlock then locki_action again
546             # so we just return after this.
547 0           locki_lock_to_client($locki,$clientvar);
548 0           return;
549             }
550             elsif (!$locki->{locked} && !defined $locki->{waiters}[0]) {
551 0           locki_delete ($locki); # locki invalid
552             }
553             }
554              
555             sub locki_lock_to_client {
556 0     0 0   my $locki = shift;
557 0           my $clientvar = shift;
558              
559 0 0         _timelog("$locki->{lock}: Issuing to $clientvar->{user}\n") if $Debug;
560 0           $locki->{locked} = 1;
561 0           $locki->{owner} = $clientvar->{user};
562 0 0         if ($clientvar->{timeout}) {
563 0           $locki->{timelimit} = $clientvar->{timeout} + fractime();
564             } else {
565 0           $locki->{timelimit} = 0;
566             }
567 0           $locki->{autounlock} = $clientvar->{autounlock};
568 0           $locki->{hostname} = $clientvar->{hostname};
569 0           $locki->{pid} = $clientvar->{pid};
570              
571 0 0 0       if ($clientvar->{locked} && $clientvar->{locks}[0] ne $locki->{lock}) {
572             # Client gave a choice of locks, and another one got to
573             # satisify it first
574 0 0         _timelog("$locki->{lock}: Already has different lock\n") if $Debug;
575 0           return locki_unlock ($locki); # locki_unlock may recurse to call locki_lock
576             }
577             else {
578             # This is the only call to a client_ routine not in the direct
579             # client call stack. Thus we may need to process more commands
580             # after this call
581 0 0         if (client_status ($clientvar)) { # sets clientvar->{locked}
582             # Worked ok
583 0           client_service($clientvar, []); # If any queued, handle more commands/ EOF
584 0           return; # Don't look for another lock waiter
585             }
586             # Else hung up, didn't get the lock, give to next guy
587 0 0         _timelog("$locki->{lock}: Owner hangup $locki->{owner}\n") if $Debug;
588 0           return locki_unlock ($locki); # locki_unlock may recurse to call locki_lock
589             }
590 0           die "%Error: Can't get here - instead we recurse thru unlock\n";
591             }
592              
593             sub locki_unlock {
594 0   0 0 0   my $locki = shift || die;
595             # Unlock this lock
596             # The locki may be deleted by this call
597 0           $locki->{locked} = 0;
598 0           $locki->{owner} = "unlocked";
599 0           $locki->{autounlock} = 0;
600 0           $locki->{hostname} = "";
601 0           $locki->{pid} = 0;
602             # Give it to someone else?
603             # Note the new lock request client may not still be around, if so we
604             # recurse back to this function with waiters one element shorter.
605 0           locki_action ($locki);
606             }
607              
608             sub locki_delete {
609 0     0 0   my $locki = shift;
610             # The locki may be deleted by this call
611 0 0         _timelog("$locki->{lock}: locki_delete\n") if $Debug;
612 0           delete $Locks{$locki->{lock}};
613             }
614              
615             sub recheck_locks {
616 0     0 0   my $self = shift;
617             # Main loop to see if any locks have changed state
618 0           my $time = fractime();
619 0 0 0       if (($self->{_recheck_locks_time}||0) < $time) {
620 0           $self->{_recheck_locks_time} = $time + $RecheckLockDelta;
621 0           foreach my $locki (values %Locks) {
622 0           locki_recheck($locki,$time); # locki may be deleted
623             }
624             }
625             }
626              
627             sub locki_recheck {
628 0     0 0   my $locki = shift;
629 0   0       my $time = shift || fractime();
630             # See if any locks need to change state due to pid disappearance or timeout
631             # The locki may be deleted by this call
632 0 0         if ($locki->{locked}) {
633 0 0 0       if ($locki->{timelimit} && ($locki->{timelimit} <= $time)) {
    0          
634 0 0         _timelog("$locki->{lock}: Timeout of $locki->{owner}\n") if $Debug;
635 0           locki_unlock ($locki); # locki may be deleted
636             }
637             elsif ($locki->{autounlock}) { # locker said it was OK to break lock if he dies
638 0 0 0       if (($locki->{autounlock_check_time}||0) < $time) {
639             # If there's 1000 locks, we don't want to check them all
640             # in one second, so scale back appropriately.
641 0           my $chkdelta = ($AutoUnlockCheckDelta
642             + ((scalar keys %Locks)/$AutoUnlockCheckPerSec));
643 0           $locki->{autounlock_check_time} = $time + $chkdelta;
644             # Only check every 2 secs or so, else we can spend more time
645             # doing the OS calls than it's worth
646 0           my $dead = undef;
647 0 0         if ($locki->{hostname} eq $Hostname) { # lock owner is running on same host
648 0           $dead = IPC::PidStat::local_pid_doesnt_exist($locki->{pid});
649 0 0         if ($dead) {
650 0 0         _timelog("$locki->{lock}: Autounlock of $locki->{owner}\n") if $Debug;
651 0           locki_unlock($locki); # break the lock, locki may be deleted
652             }
653             }
654 0 0         if (!defined $dead) {
655             # Ask the other host if the PID is gone
656             # Or, we had a permission problem so ask root.
657 0 0         _timelog("$locki->{lock}: UDP pid_request $locki->{hostname} $locki->{pid}\n") if $Debug;
658             $Exister->pid_request(host=>$locki->{hostname}, pid=>$locki->{pid},
659 0           return_exist=>0, return_doesnt=>1, return_unknown=>1);
660             # This may (or may not) return a UDP message with the status in it.
661             # If so, they will call exist_traffic.
662             }
663             }
664             }
665             }
666             }
667              
668             sub locki_new_request {
669 0   0 0 0   my $lockname = shift || "lock";
670 0           my $clientvar = shift;
671 0           my $locki;
672 0 0         if ($locki=locki_find($lockname)) {
673             # Same existing owner wants to grab it under a new connection
674 0 0 0       if ($locki->{locked} && ($locki->{owner} eq $clientvar->{user})) {
675 0 0         _timelog("c$clientvar->{client_num}: Renewing connection\n") if $Debug;
676 0           locki_lock_to_client($locki,$clientvar);
677             } else {
678             # Search waiters to see if already on list
679 0           my $found;
680 0           for (my $n=0; $n <= $#{$locki->{waiters}}; $n++) {
  0            
681             # Note the old client value != new client value, although the user is the same
682 0 0         if ($locki->{waiters}[$n]{user} eq $clientvar->{user}) {
683 0 0         _timelog("c$clientvar->{client_num}: Renewing wait list\n") if $Debug;
684 0           $locki->{waiters}[$n] = $clientvar;
685 0           $found = 1;
686 0           last;
687             }
688             }
689 0 0         if (!$found) {
690 0 0         _timelog("c$clientvar->{client_num}: New waiter\n") if $Debug;
691 0           push @{$locki->{waiters}}, $clientvar;
  0            
692             }
693             # Either way, we don't have the lock, so just hang out
694             }
695             } else { # new
696 0           $locki = {
697             lock=>$lockname,
698             locked=>0,
699             owner=>"unlocked",
700             waiters=>[$clientvar],
701             };
702 0           $Locks{$lockname} = $locki;
703 0 0         _timelog("$locki->{lock}: New\n") if $Debug;
704             # Process it, which will establish the lock for this client
705 0           locki_action($locki);
706             }
707 0           return $locki;
708             }
709              
710             sub locki_find {
711 0   0 0 0   return $Locks{$_[0] || "lock"};
712             }
713              
714             sub DESTROY {
715 0     0     my $self = shift;
716 0 0         _timelog("DESTROY\n") if $Debug;
717 0 0 0       if (($self->{family} eq 'UNIX') && $self->{unix_socket_created}){
718 0           unlink $self->{port};
719             }
720             }
721              
722             ######################################################################
723             #### Logging
724              
725             sub _timelog {
726 0     0     IPC::Locker::_timelog(@_);
727             }
728             sub _timelog_split {
729 0     0     IPC::Locker::_timelog_split(@_);
730             }
731              
732             ######################################################################
733             #### Package return
734             1;