File Coverage

blib/lib/Patro/Archy.pm
Criterion Covered Total %
statement 504 706 71.3
branch 293 422 69.4
condition 52 114 45.6
subroutine 45 50 90.0
pod 0 25 0.0
total 894 1317 67.8


line stmt bran cond sub pod time code
1             package Patro::Archy;
2 69     69   1252 use strict;
  69         113  
  69         1801  
3 69     69   294 use warnings;
  69         94  
  69         1517  
4 69     69   261 use Carp;
  69         96  
  69         3228  
5 69     69   4776 eval "use Sys::HostAddr";
  0         0  
  0         0  
6 69     69   309 use Socket ();
  69         97  
  69         948  
7 69     69   272 use Scalar::Util 'reftype';
  69         102  
  69         2664  
8 69     69   18746 use POSIX ':sys_wait_h';
  69         307606  
  69         307  
9             require overload;
10              
11             our $threads_avail;
12       2960     *sxdiag = sub {};
13             if ($ENV{PATRO_SERVER_DEBUG}) {
14             *sxdiag = *::xdiag;
15             our $DEBUG = 1;
16             }
17             our $VERSION = '0.15';
18 69     69   111199 our @SERVERS :shared;
  69         62617  
  69         514  
19             our %OPTS = ( # XXX - needs documentation
20             keep_alive => 30,
21             idle_timeout => 30,
22             fincheck_freq => 5,
23             );
24              
25             sub new {
26 89     68 0 24983 my $pkg = shift;
27 68         140 my $opts = shift;
28              
29 68   33     371 my $host = $ENV{HOSTNAME} // qx(hostname) // "localhost";
      50        
30 68 50       269 if ($INC{'Sys/HostAddr.pm'}) {
31 0         0 my $host2 = Sys::HostAddr->new->main_ip;
32 0 50       0 $host = $host2 if $host2;
33             }
34 68         172 chomp($host);
35              
36 68 50       20068 socket(my $socket, Socket::PF_INET(), Socket::SOCK_STREAM(),
37             getprotobyname("tcp")) || croak __PACKAGE__, " socket: $!";
38 68 50       1069 setsockopt($socket, Socket::SOL_SOCKET(), Socket::SO_REUSEADDR(),
39             pack("l",1)) || croak __PACKAGE__, " setsockopt: $!";
40 68         8086 my $sockaddr = Socket::pack_sockaddr_in(0, Socket::inet_aton($host));
41 68 50       1140 bind($socket, $sockaddr) || croak __PACKAGE__, " bind: $!";
42 68 50       553 listen($socket, Socket::SOMAXCONN()) || croak __PACKAGE__, " listen: $!";
43 68         367 $sockaddr = getsockname($socket);
44 68         349 my ($port,$addr) = Socket::unpack_sockaddr_in($sockaddr);
45              
46             my $meta = {
47             sockaddr => $sockaddr,
48             socket => $socket,
49             host => $host,
50             host2 => Socket::inet_aton($addr),
51             port => $port,
52              
53             creator_pid => $$,
54             creator_tid => $threads_avail && threads->tid,
55             style => $threads_avail ? 'threaded' : 'forked',
56              
57             keep_alive => $OPTS{keep_alive},
58             idle_timeout => $OPTS{idle_timeout},
59 68 50 33     692156 version => $Patro::Archy::VERSION,
60             };
61              
62 68         459 $Patro::SERVER_VERSION = $Patro::Archy::VERSION;
63              
64 68         177 my $obj = {};
65 68         150 my @store;
66              
67 68 50       257 if ($threads_avail) {
68 0         0 for (@_) {
69 0         0 local $threads::shared::clone_warn = undef;
70 0         0 eval { $_ = threads::shared::shared_clone($_) };
  0         0  
71 0 0       0 if ($@ =~ /CODE|GLOB/) {
72 0         0 require Patro::LeumJelly;
73 0         0 warn $@;
74 0         0 $threads::shared::clone_warn = 0;
75 0         0 $_ = threads::shared::shared_clone($_);
76             }
77             }
78             }
79 68         350 foreach my $o (@_) {
80 113         276 my ($num,$str);
81             {
82 69     69   27106 no overloading;
  69         130  
  69         2287  
  113         183  
83 69     69   335 no warnings 'portable';
  69         132  
  69         83687  
84 113         348 $str = "$o";
85 113         960 ($num) = $str =~ /x(\w+)/;
86 113         455 $num = hex($num);
87             }
88 113         382 $obj->{$num} = $o;
89 113         584 my $reftype = Scalar::Util::reftype($o);
90 113         260 my $ref = CORE::ref($o);
91 113 50       514 if ($ref eq 'threadsx::shared::code') {
    50          
92 0         0 $ref = $reftype = 'CODE*';
93             } elsif ($ref eq 'threadsx::shared::glob') {
94 0         0 $ref = $reftype = 'GLOB';
95             }
96 113         493 my $store = {
97             ref => $ref,
98             reftype => $reftype,
99             id => $num
100             };
101 113 100       473 if (overload::Overloaded($o)) {
102 21 50 33     1104 if ($ref ne 'CODE' && $ref ne 'CODE*' && $ref ne 'GLOB') {
      33        
103 21         66 $store->{overload} = _overloads($o);
104             }
105             }
106 113         4469 push @store, $store;
107             }
108 68         530 my $self = bless {
109             meta => $meta,
110             store => \@store,
111             obj => $obj
112             }, __PACKAGE__;
113 68         414 $self->{config} = $self->config;
114 68         462 $self->start_server;
115 21         217 eval { push @SERVERS, $self };
  21         230  
116 21 50       197 warn $@ if $@;
117 21 50       186 if (@SERVERS == 1) {
118 21         4309 eval q~END {
119             if ($Patro::Archy::threads_avail) {
120             $_->detach for threads->list(threads::running);
121             }
122             }~;
123             }
124 21         770 return $self;
125             }
126              
127              
128             sub start_server {
129 68     68 0 141 my $self = shift;
130 68         156 my $meta = $self->{meta};
131 68 50       278 if ($meta->{style} eq 'threaded') {
132 0         0 my $server_thread;
133             $server_thread = threads->create(
134             sub {
135 0     0   0 $SIG{KILL} = sub { exit };
  0         0  
136 0         0 $SIG{CHLD} = sub { $self->watch_for_finishers(@_) };
  0         0  
137 0         0 $SIG{ALRM} = sub { $self->watch_for_finishers(@_) };
  0         0  
138 0 0       0 if ($self->{meta}{pid_file}) {
139 0         0 open my $fh, '>>', $self->{meta}{pid_file};
140 0         0 flock $fh, 2;
141 0         0 seek $fh, 0, 2;
142 0         0 print {$fh} "$$-", threads->tid, "\n";
  0         0  
143 0         0 close $fh;
144             }
145 0         0 $self->accept_clients;
146 0         0 return;
147 0         0 } );
148 0         0 $self->{meta}{server_thread} = $server_thread;
149 0         0 $self->{meta}{server_pid} = $$;
150 0         0 $self->{meta}{server_tid} = $server_thread->tid;
151             #$server_thread->detach;
152             } else {
153 68         59922 my $pid = CORE::fork();
154 68 50       3520 if (!defined($pid)) {
155 0         0 croak __PACKAGE__, " fork: $!";
156             }
157 68 100       1161 if ($pid == 0) {
158 47 50       1058 if ($self->{meta}{pid_file}) {
159 0         0 open my $fh, '>>', $self->{meta}{pid_file};
160 0         0 flock $fh, 2;
161 0         0 seek $fh, 0, 2;
162 0         0 print {$fh} "$$\n";
  0         0  
163 0         0 close $fh;
164             }
165 47         1781 $self->accept_clients;
166 21         4837 exit;
167             }
168 21         744 $self->{meta}{server_pid} = $pid;
169             }
170             }
171              
172             # return list of operators that are overloaded on the given object
173             my @oplist;
174             sub _overloads {
175 25     25   47 my $obj = shift;
176 25 50       65 return unless overload::Overloaded($obj);
177 25 100       821 if (!@oplist) {
178 9         198 @oplist = split ' ',join(' ',values %overload::ops);
179             }
180              
181 25         76 my %ops = map { $_ => 1 } grep { overload::Method($obj,$_) } @oplist;
  643         1890  
  1875         51189  
182              
183             # we also need to account for the operations that are *implicitly*
184             # overloaded.
185              
186             # Many ops can be generated out of 0+, "", or bool
187 25 50 66     177 if ($ops{"0+"} || $ops{'""'} || $ops{bool}) {
      33        
188 25         233 $ops{$_}++ for qw(0+ "" bool int ! qr . x .= x= <> -X);
189             }
190              
191             # assignment ops can be generated from binary ops
192 25         53 foreach my $binop (qw(. x + - * / ** % & | ^ << >> &. |. ^.)) {
193 400 100       660 $ops{$binop . "="}++ if $ops{$binop};
194             }
195              
196             # all comparison ops can be generated from <=> and cmp
197 25 100       95 @ops{qw{< <= > >= == !=}} = (1) x 6 if $ops{"<=>"};
198 25 100       122 @ops{qw(le lt ge gt eq ne)} = (1) x 6 if $ops{cmp};
199              
200 25 100       64 $ops{neg}++ if $ops{"-"};
201 25 100       47 $ops{"--"}++ if $ops{"-="};
202 25 50 66     91 $ops{abs}++ if $ops{"<"} && $ops{neg};
203 25 100       59 $ops{"++"}++ if $ops{"+="};
204              
205             # all ops are overloaded if there is a 'nomethod' specified
206 25 50       53 @ops{@oplist} = (1) x @oplist if $ops{nomethod};
207 25         406 return [keys %ops];
208             }
209              
210             sub config {
211 68     68 0 169 my $self = shift;
212             my $config_data = {
213             host => $self->{meta}{host},
214             port => $self->{meta}{port},
215             store => $self->{store},
216             style => $self->{meta}{style},
217 68         856 version => $Patro::Archy::VERSION
218             };
219 68         528 return bless $config_data, 'Patro::Config';
220             }
221              
222             ########################################
223              
224             sub Patro::Config::to_string {
225 10     10   5183 my ($self) = @_;
226 10         361 return Patro::LeumJelly::serialize({%$self});
227             }
228              
229             sub Patro::Config::to_file {
230 2     2   1388 my ($self,$file) = @_;
231 2 50       28 if (!$file) {
232             # TODO: select a temp filename
233             }
234 2         6 my $fh;
235 2 50       254 if (!open($fh, '>', $file)) {
236 0         0 croak "Patro::Config::to_file: could not write cfga file '$file': $!";
237             }
238 2         14 print {$fh} $self->to_string;
  2         15  
239 2         286 close $fh;
240 2         18 return $file;
241             }
242              
243             sub Patro::Config::from_string {
244 12     12   46 my ($self, $string) = @_;
245 12         57 my $cfg = Patro::LeumJelly::deserialize($string);
246 12         484 return bless $cfg, 'Patro::Config';
247             }
248              
249             sub Patro::Config::from_file {
250 3     3   10 my ($self, $file) = @_;
251 3 50 33     17 if (!defined($file) && !CORE::ref($self) && $self ne 'Patro::Config') {
      33        
252 0         0 $file = $self;
253             }
254 3         6 my $fh;
255 3 50       105 if (CORE::ref($file) eq 'GLOB') {
    50          
256 0         0 $fh = $file;
257             } elsif (!open $fh, '<' ,$file) {
258 0         0 croak "Patro::Config::fron_file: could not read cfg file '$file': $!";
259             }
260 3         57 my $data = <$fh>;
261 3         28 close $fh;
262 3         24 return Patro::Config->from_string($data);
263             }
264              
265             ########################################
266              
267             sub accept_clients {
268             # accept connection from client
269             # spin off connection to separate thread or process
270             # perform request_response_loop on the client connection
271 47     47 0 346 my $self = shift;
272 47         430 my $meta = $self->{meta};
273              
274 47         554 $meta->{last_connection} = time;
275 47         429 $meta->{finished} = 0;
276              
277 47         494 while (!$meta->{finished}) {
278 97     27   5463 $SIG{CHLD} = sub { $self->watch_for_finishers(@_) };
  27         558  
279 97     4   1248 $SIG{ALRM} = sub { $self->watch_for_finishers(@_) };
  4         75  
280 97   50     1756 alarm ($OPTS{fincheck_freq} || 5);
281 97         324 my $client;
282 97         408 my $server = $meta->{socket};
283 97         38898685 my $paddr = accept($client,$server);
284 97 100       2062 if (!$paddr) {
285 30 50   69   758 next if $!{EINTR};
  69         18345  
  69         74127  
  69         519  
286 0 0 0     0 next if $!{ECHILD} || $!==10; # !?! why $!{ECHILD} not suff on Lin?
287 0         0 ::xdiag("accept failure, %errno is",\%!);
288 0         0 croak __PACKAGE__, ": accept ", 0+$!," $!";
289             }
290 67         315 $meta->{last_connection} = time;
291              
292 67         836 $self->start_subserver($client);
293 41         629 $self->watch_for_finishers('MAIN');
294             }
295             }
296              
297             sub start_subserver {
298 67     67 0 322 my ($self,$client) = @_;
299 67 50       603 if ($self->{meta}{style} eq 'forked') {
300 67         48064 my $pid = CORE::fork();
301 67 50       2223 if (!defined($pid)) {
302 0         0 croak __PACKAGE__,": fork after accept $!";
303             }
304 67 100       1028 if ($pid != 0) {
305 41 50       417 if ($self->{meta}{pid_file}) {
306 0         0 open my $fh, '>>', $self->{meta}{pid_file};
307 0         0 flock $fh, 2;
308 0         0 seek $fh, 0, 2;
309 0         0 print {$fh} "$pid\n";
  0         0  
310 0         0 close $fh;
311             }
312 41         1020 $self->{meta}{pids}{$pid}++;
313 41         867 return;
314             }
315 26         942 $self->request_response_loop($client);
316 26         22933 exit;
317             } else {
318             my $subthread = threads->create(
319             sub {
320 0     0   0 $self->request_response_loop($client);
321 0         0 threads->self->detach;
322 0         0 return;
323 0         0 } );
324 0 0       0 if ($self->{meta}{pid_file}) {
325 0         0 open my $fh, '>>', $self->{meta}{pid_file};
326 0         0 flock $fh, 2;
327 0         0 seek $fh, 0, 2;
328 0         0 print {$fh} "$$-", $subthread->tid, "\n";
  0         0  
329 0         0 close $fh;
330             }
331 0         0 $self->{meta}{pids}{"$$-" . $subthread->tid}++;
332 0         0 push @{$self->{meta}{subthreads}}, $subthread;
  0         0  
333              
334             # $subthread->detach ?
335              
336 0         0 return;
337             }
338             }
339              
340             sub watch_for_finishers {
341 72     72 0 671 my ($self,$sig) = @_;
342 72         479 alarm 0;
343              
344             # XXX - how do you know when a thread is finished?
345             # what if it is a detached thread?
346              
347 72   66     2547 while ((my $pid = waitpid(-1,WNOHANG())) > 0 && WIFEXITED($?)) {
348 27         429 delete $self->{meta}{pids}{$pid};
349             }
350 72 50       478 if ($self->{meta}{subthreads}) {
351 0         0 my $n = @{$self->{meta}{subthreads}};
  0         0  
352 0         0 my $n1 = threads->list(threads::all());
353 0         0 my $n2 = threads->list(threads::running());
354 0         0 my @joinable = threads->list(threads::joinable());
355 0 0       0 if (@joinable) {
356 0         0 foreach my $subthread (@joinable) {
357             my ($i) = grep {
358 0         0 $self->{meta}{subthreads}{$_} == $subthread
  0         0  
359             } 0 .. $n-1;
360 0 0       0 if (!defined($i)) {
361 0         0 warn "subthread $subthread not found on this server!";
362 0         0 next;
363             }
364 0         0 $self->{meta}{subthreads}[$i]->join;
365 0         0 $self->{meta}{subthreads}[$i] = undef;
366             }
367             $self->{meta}{subthreads} =
368 0         0 [ grep { defined } @{$self->{meta}{subthreads} } ];
  0         0  
  0         0  
369             }
370             }
371 72 100       512 unless ($self->still_active) {
372 21         121 $self->{meta}{finished}++;
373             }
374 72     0   2124 $SIG{ALRM} = sub { $self->watch_for_finishers(@_) };
  0         0  
375 72     0   1525 $SIG{CHLD} = sub { $self->watch_for_finishers(@_) };
  0         0  
376 72   50     1793 alarm ($OPTS{fincheck_freq} || 5);
377             }
378              
379             sub still_active {
380 72     72 0 246 my $self = shift;
381 72         315 my $meta = $self->{meta};
382 72 50       392 if (time <= $meta->{keep_alive}) {
383 0         0 return 1;
384             }
385 72 100       404 if (time < $meta->{last_connection} + $meta->{idle_timeout}) {
386 42         299 return 1;
387             }
388 30 100       118 if (keys %{$meta->{pids}}) {
  30         308  
389 9         58 return 1;
390             }
391 21         205 return;
392             }
393              
394             sub request_response_loop {
395 26     26 0 242 my ($self, $client) = @_;
396              
397 26         270 local $Patro::Archy::disconnect = 0;
398 26         984 my $fh0 = select $client;
399 26         642 $| = 1;
400 26         394 select $fh0;
401              
402 26         103788 while (my $req = readline($client)) {
403 568 50       5511 next unless $req =~ /\S/;
404 568         3797 sxdiag("server: got request '$req'");
405 568         2166 my $resp = $self->process_request($req);
406 568         1769 sxdiag("server: response to request is ",$resp);
407 568         1521 $resp = $self->serialize_response($resp);
408 568         2126 sxdiag("server: serialized response to request is ",$resp);
409 568         855 print {$client} $resp,"\n";
  568         25834  
410 568 100       24171257 last if $Patro::Archy::disconnect;
411             }
412 26         1277 close $client;
413 26         330 return;
414             }
415              
416             our $SIDES; # for the server to activate or suppress some
417             # side-effects from the lower levels of the
418             # request handler
419              
420             sub process_request {
421 568     568 0 1488 my ($self,$request) = @_;
422 568 50       2808 croak "process_request: expected scalar request" if ref($request);
423              
424 568         1800 $request = Patro::LeumJelly::deserialize($request);
425 568         16316 my $topic = $request->{topic};
426 568 50       1426 if (!defined($topic)) {
427 0         0 return $self->error_response("bad topic in request '$_[1]'");
428             }
429            
430 568         1021 my $has_args = $request->{has_args};
431 568         957 my $args = $request->{args};
432 568 100       1340 if ($request->{has_args}) {
433 363         596 local $@;
434             $args = [ map {
435 501 100       1119 if (CORE::ref($_) eq '.Patroon') {
436 5         9 eval { $self->{obj}{$$_} };
  5         17  
437             } else {
438 496         1553 $_
439 363         516 } } @{$request->{args}} ];
  363         1126  
440 363 50       1012 if ($@) {
441 0         0 return $self->error_response($@);
442             }
443             }
444 568         1054 my $id = $request->{id};
445 568         975 my $cmd = $request->{command};
446 568         936 my $ctx = $request->{context};
447 568 100       1581 my @orig_args = $has_args ? @$args : ();
448 568 100       1308 my @orig_refs = $has_args ? \ (@$args) : ();
449 568         1714 my @orig_dump = map Patro::LeumJelly::serialize([$_]), @$args;
450 568         16611 local $! = 0;
451 568         2110 local $? = 0;
452 568         1254 local $SIDES = {};
453 568         936 my @r;
454 568         730 our $DEBUG;
455 568   50     3243 local $DEBUG = $DEBUG || $request->{_debug} || 0;
456              
457 568 100       2154 if ($topic eq 'META') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
458 67         390 @r = $self->process_request_META($id,$cmd,$ctx,$has_args,$args);
459             } elsif ($topic eq 'HASH') {
460 70         350 @r = $self->process_request_HASH($id,$cmd,$ctx,$has_args,$args);
461             } elsif ($topic eq 'ARRAY') {
462 249         790 @r = $self->process_request_ARRAY($id,$cmd,$ctx,$has_args,$args);
463             } elsif ($topic eq 'SCALAR') {
464 20         72 @r = $self->process_request_SCALAR($id,$cmd,$ctx,$has_args,$args);
465             } elsif ($topic eq 'METHOD') {
466 32         164 @r = $self->process_request_METHOD($id,$cmd,$ctx,$has_args,$args);
467             } elsif ($topic eq 'CODE') {
468 3         44 @r = $self->process_request_CODE($id,undef,$ctx,$has_args,$args);
469             } elsif ($topic eq 'HANDLE') {
470 85         261 @r = $self->process_request_HANDLE($id,$cmd,$ctx,$has_args,$args);
471             } elsif ($topic eq 'OVERLOAD') {
472 36         94 my $obj = $self->{obj}{$id};
473 36         125 @r = $self->process_request_OVERLOAD($obj,$cmd,$args,$ctx);
474             } elsif ($topic eq 'REF') {
475 6         30 @r = $self->process_request_REF($id,$cmd,$ctx,$has_args,$args);
476             } else {
477 0         0 @r = ();
478 0         0 $@ = __PACKAGE__ . ": unrecognized topic '$topic' in proxy request";
479             }
480 568 100 100     2688 if (@r && CORE::ref($r[0]) eq '.Patroclus') {
481 67         482 return $r[0];
482             }
483 501         1687 my $sides = bless {}, '.Patroclus';
484              
485 501 100       1852 $sides->{errno} = 0 + $! if $!;
486 501 100       1638 $sides->{errno_extended} = $^E if $^E;
487 501 100       1138 $sides->{child_error} = $? if $?;
488 501 100       1076 $sides->{error} = $@ if $@;
489              
490             # how to update elements of @_ that have changes?
491             # three implementations below. Pick one.
492             # 1. "side A" - return all elements of @_. You will have to
493             # filter out "Modification of a read-only element attempted ..."
494             # messages
495             # 2. "side B" - do a deep comparison of original and final
496             # elements of @_, return the ones that mismatch I CHOOSE YOU!
497             # 3. original implementation - do shallow comparison of original
498             # and final elements of @_. Insufficient for code that updates
499             # nested data of the inputs
500 501         1281 my (@out,@outref);
501              
502             # "sideB" - do a deep compare for all arguments
503 501   100     2283 for (my $j=0; $j<@$args && !$SIDES->{no_out}; $j++) {
504 431         1616 my $dj = Patro::LeumJelly::serialize([$args->[$j]]);
505 431         12957 for (my $i=0; $i<@orig_refs; $i++) {
506 1038 100       2770 next if $orig_refs[$i] != \$args->[$j];
507 431 100       1750 if ($orig_dump[$i] ne $dj) {
508 14         47 push @out, $i, $args->[$j];
509             }
510             }
511             }
512 501         1257 $sides->{sideB} = 1;
513              
514 501 100       1070 $sides->{out} = \@out if @out;
515 501 50       1017 $sides->{outref} = \@outref if @outref;
516 501 100 100     2470 if ($ctx >= 2) {
    100          
517 15         62 return $self->list_response($sides, @r);
518             } elsif ($ctx == 1 && defined $r[0]) {
519 448         1467 my $y = $self->scalar_response($sides, $r[0]);
520             # if ($topic eq 'REF') { ::xdiag("response:",$y) }
521 448         4048 return $y;
522             } else {
523 38         179 return $self->void_response($sides);
524             }
525             }
526              
527             sub process_request_META {
528 67     67 0 273 my ($self,$id,$cmd,$ctx,$has_args,$args) = @_;
529 67 100       250 if ($cmd eq 'disconnect') {
530 5         16 $Patro::Archy::disconnect = 1;
531 5         35 return bless { disconnect_ok => 1 }, '.Patroclus';
532             }
533 62         222 my $obj = $self->{obj}{$id};
534 62 50       732 if ($cmd eq 'ref') {
    50          
    50          
535 0         0 return CORE::ref($obj);
536             } elsif ($cmd eq 'reftype') {
537 0         0 return Scalar::Util::reftype($obj);
538             } elsif ($cmd eq 'destroy') {
539 62         339 delete $self->{obj}{$id};
540 62         365 my @ids = keys %{$self->{obj}};
  62         332  
541 62 100       391 if (@ids == 0) {
542 16         103 $Patro::Archy::disconnect = 1;
543 16         154 return bless { disconnect_ok => 1 }, '.Patroclus';
544             } else {
545 46         387 return bless { disconnect_ok => 0,
546             num_reminaing_objs => 0+@ids }, '.Patroclus';
547             }
548             } else {
549 0         0 $@ = "Patro: unsupported meta command '$cmd'";
550 0         0 return;
551             }
552             }
553              
554             sub process_request_HASH {
555 70     70 0 227 my ($self,$id,$cmd,$ctx,$has_args,$args) = @_;
556 70         241 my $obj = $self->{obj}{$id};
557 70 50       421 if (reftype($obj) ne 'HASH') {
558 0         0 $@ = "Not a HASH reference";
559 0         0 return;
560             # !!! what if '%{}' op is overloaded?
561             }
562 70 100       234 if ($cmd eq 'STORE') {
    100          
    100          
    50          
    0          
    0          
    0          
    0          
563 7         27 my ($key,$val) = @$args;
564 7         32 my $old_val = $obj->{$key};
565 7         97 $obj->{$key} = threads::shared::shared_clone($val);
566 7         34 return $old_val;
567             } elsif ($cmd eq 'FETCH') {
568 53         363 return $obj->{$args->[0]};
569             } elsif ($cmd eq 'DELETE') {
570 2         38 return delete $obj->{$args->[0]};
571             } elsif ($cmd eq 'EXISTS') {
572 8         33 return exists $obj->{$args->[0]};
573             } elsif ($cmd eq 'CLEAR') {
574 0         0 %$obj = ();
575 0         0 return;
576             } elsif ($cmd eq 'FIRSTKEY') {
577 0         0 keys %$obj;
578 0         0 my ($k,$v) = each %$obj;
579 0         0 return $k;
580             } elsif ($cmd eq 'NEXTKEY') {
581 0         0 my ($k,$v) = each %$obj;
582 0         0 return $k;
583             } elsif ($cmd eq 'SCALAR') {
584 0         0 return scalar %$obj;
585             } else {
586 0         0 $@ = "HASH function '$cmd' not recognized";
587 0         0 return;
588             }
589             }
590              
591             sub process_request_ARRAY {
592 249     249 0 722 my ($self,$id,$cmd,$ctx,$has_args,$args) = @_;
593 249         592 my $obj = $self->{obj}{$id};
594 249 50       927 if (reftype($obj) ne 'ARRAY') {
595 0         0 $@ = "Not an ARRAY ref";
596 0         0 return;
597             }
598 249 100 33     848 if ($cmd eq 'STORE') {
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    0          
599 7         21 my ($index,$val) = @$args;
600 7         17 my $old_val = $obj->[$index];
601             # ?!!!? does $val have to be shared?
602 7         13 eval { $obj->[$index] = threads::shared::shared_clone($val) };
  7         59  
603 7         48 return $old_val;
604             } elsif ($cmd eq 'FETCH') {
605 180         366 return eval { $obj->[$args->[0]] };
  180         718  
606             } elsif ($cmd eq 'FETCHSIZE') {
607 32         107 return scalar @$obj;
608             } elsif ($cmd eq 'STORESIZE' || $cmd eq 'EXTEND') {
609 0         0 my $n = $#{$obj} = $args->[0]-1;
  0         0  
610 0         0 return $n+1;
611             } elsif ($cmd eq 'SPLICE') {
612 16         52 my ($off,$len,@list) = @$args;
613 16 100       43 if ($off < 0) {
614 3         8 $off += @$obj;
615 3 50       11 if ($off < 0) {
616 0         0 $@ = "Modification of non-createable array value attempted, "
617             . "subscript $off";
618 0         0 return;
619             }
620             }
621 16 100 66     107 if (!defined($len) || $len eq 'undef') {
622 2         3 $len = @{$obj} - $off;
  2         6  
623             }
624 16 100       47 if ($len < 0) {
625 2         5 $len += @{$obj} - $off;
  2         5  
626 2 50       9 if ($len < 0) {
627 0         0 $len = 0;
628             }
629             }
630 16         34 my @val = splice @{$obj}, $off, $len, @list;
  16         139  
631 16         56 $SIDES->{no_out} = 1; # don't try to update @_
632             # SPLICE is the only ARRAY function that doesn't assume scalar context
633 16 100       49 if ($ctx == 1) {
634 6 50       55 return @val > 0 ? $val[-1] : undef;
635             } else {
636 10         45 return @val;
637             }
638             } elsif ($cmd eq 'PUSH') {
639 6         11 return push @{$obj}, map threads::shared::shared_clone($_), @$args;
  6         62  
640             } elsif ($cmd eq 'UNSHIFT') {
641 2         6 return unshift @{$obj}, map threads::shared::shared_clone($_), @$args;
  2         11  
642             } elsif ($cmd eq 'POP') {
643 3         6 return pop @{$obj};
  3         11  
644             } elsif ($cmd eq 'SHIFT') {
645 3         8 return shift @{$obj};
  3         12  
646             } elsif ($cmd eq 'EXISTS') {
647 0         0 return exists $obj->[$args->[0]];
648             } else {
649 0         0 $@ = "tied ARRAY function '$cmd' not recognized";
650 0         0 return;
651             }
652             }
653              
654             sub process_request_SCALAR {
655 20     20 0 70 my ($self,$id,$cmd,$ctx,$has_args,$args) = @_;
656 20         52 my $obj = $self->{obj}{$id};
657 20 50       105 if (reftype($obj) ne 'SCALAR') {
658 0         0 $@ = "Not a SCALAR reference";
659 0         0 return;
660             }
661 20 100       66 if ($cmd eq 'STORE') {
    50          
662 6         11 my $val = ${$obj};
  6         16  
663 6         118 ${$obj} = threads::shared::shared_clone($args->[0]);
  6         18  
664 6         128 return $val;
665             } elsif ($cmd eq 'FETCH') {
666 14         25 return ${$obj};
  14         62  
667             } else {
668 0         0 $@ = "tied SCALAR function '$cmd' not recognized";
669 0         0 return;
670             }
671             }
672              
673             sub process_request_METHOD {
674 32     32 0 108 my ($self,$id,$command,$context,$has_args,$args) = @_;
675 32         87 my $obj = $self->{obj}{$id};
676 32 50       108 if (!$obj) {
677 0         0 $@ = "Bad object id '$id' in proxy method call";
678 0         0 return;
679             }
680 32         81 my @r;
681 32 100       197 if ($command =~ /::/) {
    100          
682 69     69   159677 no strict 'refs';
  69         154  
  69         128227  
683 1 50       7 if ($context < 2) {
684 1 50       4 @r = scalar eval { $has_args ? &$command($obj,@$args)
  1         18  
685             : &$command($obj) };
686             } else {
687 0 0       0 @r = eval { $has_args ? &$command($obj,@$args)
  0         0  
688             : &$command($obj) };
689             }
690             } elsif ($context < 2) {
691 30 100       51 @r = scalar eval { $has_args ? $obj->$command(@$args)
  30         543  
692             : $obj->$command };
693             } else {
694 1 50       3 @r = eval { $has_args ? $obj->$command(@$args)
  1         8  
695             : $obj->$command };
696             }
697 32         545 return @r;
698             }
699              
700             sub process_request_HANDLE {
701 85     85 0 206 my ($self,$id,$command,$context,$has_args,$args) = @_;
702 85         195 my $obj = $self->{obj}{$id};
703 85 50       190 my $fh = CORE::ref($obj) eq 'threadsx::shared::glob' ? $obj->glob : $obj;
704 85 100 66     781 if ($command eq 'PRINT') {
    100 100        
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
705 7         13 my $z = print {$fh} @$args;
  7         190  
706 7         27 return $z;
707             } elsif ($command eq 'PRINTF') {
708 1 50       3 if ($has_args) {
709 1         2 my $template = shift @$args;
710 1         2 my $z = printf {$fh} $template, @$args;
  1         20  
711 1         4 return $z;
712             } else {
713             # I don't think we can get here through the proxy
714 0         0 my $z = printf {$fh} "";
  0         0  
715 0         0 return $z;
716             }
717             } elsif ($command eq 'WRITE') {
718 0 0       0 if (@$args < 2) {
719 0         0 return $self->error_response("Not enough arguments for syswrite");
720             }
721 0   0     0 return syswrite($fh, $args->[0],
      0        
722             $args->[1] // undef, $args->[2] // undef);
723             } elsif ($command eq 'READLINE') {
724 13         19 my @val;
725 13 100       27 if ($context > 1) {
726 2         20 my @val = readline($fh);
727 2         9 return @val;
728             } else {
729 11         443 my $val = readline($fh);
730 11         41 return $val;
731             }
732             } elsif ($command eq 'GETC') {
733 5         18 my $ch = getc($fh);
734 5         16 return $ch;
735             } elsif ($command eq 'READ' || $command eq 'READ?' ||
736             $command eq 'SYSREAD') {
737 3         11 local $Patro::read_sysread_flag; # don't clobber
738 3 50       8 if (@$args < 2) {
739             # I don't think we can get here through the proxy
740 0         0 $@ = "Not enough arguments for " . lc($command);
741 0         0 return;
742             }
743 3         14 my (undef, $len, $off) = @$args;
744 3         5 my $z;
745 3 100 33     18 if ($command eq 'SYSREAD' ||
      66        
746             ($command eq 'READ?' && fileno($fh) >= 0)) {
747 1   50     9 $z = sysread $fh, $args->[0], $len, $off || 0;
748             } else {
749             # sysread doesn't work, for example, on file handles opened
750             # from a reference to a scalar
751 2   50     22 $z = read $fh, $args->[0], $len, $off || 0;
752             }
753 3         13 return $z;
754             } elsif ($command eq 'EOF') {
755 2         17 return eof($fh);
756             } elsif ($command eq 'FILENO') {
757 1         6 my $z = fileno($fh);
758 1         3 return $z;
759             } elsif ($command eq 'SEEK') {
760 2 50       10 if (@$args < 2) {
    50          
761 0         0 $@ = "Not enough arguments for seek";
762 0         0 return;
763             } elsif (@$args > 2) {
764 0         0 $@ = "Too many arguments for seek";
765 0         0 return;
766             } else {
767 2         12 my $z = seek $fh, $args->[0], $args->[1];
768 2         9 return $z;
769             }
770             } elsif ($command eq 'TELL') {
771 8         21 my $z = tell($fh);
772 8         22 return $z;
773             } elsif ($command eq 'BINMODE') {
774 3         5 my $z;
775 3 100       6 if (@$args) {
776 2         108 $z = binmode $fh, $args->[0];
777             } else {
778 1         11 $z = binmode $fh;
779             }
780 3         230 return $z;
781             } elsif ($command eq 'CLOSE') {
782 8 50       24 if ($Patro::SECURE) {
783 0         0 $@ = "Patro: insecure CLOSE operation on proxy filehandle";
784 0         0 return;
785             }
786 8         146 my $z = close $fh;
787 8         30 return $z;
788             } elsif ($command eq 'OPEN') {
789 5 50       21 if ($Patro::SECURE) {
790 0         0 $@ = "Patro: insecure OPEN operation on proxy filehandle";
791 0         0 return;
792             }
793 5         9 my $z;
794 5         10 my $mode = shift @$args;
795 5 100       15 if (@$args == 0) {
796 1         25 $z = open $fh, $mode;
797             } else {
798 4         5 my $expr = shift @$args;
799 4 100       14 if (@$args == 0) {
800 3         175 $z = open $fh, $mode, $expr;
801             } else {
802 1         2519 $z = open $fh, $mode, $expr, @$args;
803             }
804             }
805              
806             # it is hard to set autoflush from the proxy.
807             # Since it is usually what you want, let's do it here.
808 5 50       25 if ($z) {
809 5         24 my $fh_sel = select $fh;
810 5         20 $| = 1;
811 5         18 select $fh_sel;
812             }
813 5         24 return $z;
814             }
815             # commands that are not in the tied filehandle
816             elsif ($command eq 'TRUNCATE') {
817 1         5 my $z = truncate $fh, $args->[0];
818 1         4 return $z;
819             } elsif ($command eq 'FCNTL') {
820 0         0 my $z = fcntl $fh, $args->[0], $args->[1];
821 0         0 return $z;
822             } elsif ($command eq 'FLOCK') {
823 4         21 my $z = flock $fh, $args->[0];
824 4         19 return $z;
825             } elsif ($command eq 'STAT') {
826 2 100       6 if ($context < 2) {
827 1         22 return scalar stat $fh;
828             } else {
829 1         5 return stat $fh;
830             }
831             } elsif ($command eq 'LSTAT') {
832 0 0       0 if ($context < 2) {
833 0         0 return scalar lstat $fh;
834             } else {
835 0         0 return lstat $fh;
836             }
837             } elsif ($command eq '-X') {
838 6         10 my $key = $args->[0];
839 6         492 return eval "-$key \$fh";
840             } elsif ($command eq 'SYSOPEN') {
841 3 50       7 if ($Patro::SECURE) {
842 0         0 $@ = "Patro: insecure SYSOPEN operation on proxy filehandle";
843 0         0 return;
844             }
845 3 50       26 my $z = @$args <= 2 ? sysopen $fh, $args->[0], $args->[1]
846             : sysopen $fh, $args->[0], $args->[1], $args->[2];
847 3         12 return $z;
848              
849             # commands that operate on DIRHANDLEs
850             } elsif ($command eq 'OPENDIR') {
851 1 50       5 if ($Patro::SECURE) {
852 0         0 $@ = "Patro: insecure OPENDIR operation on proxy dirhandle";
853 0         0 return;
854             }
855 1         13 return opendir $fh, $args->[0];
856             } elsif ($command eq 'REWINDDIR') {
857 1         5 return rewinddir $fh;
858             } elsif ($command eq 'TELLDIR') {
859 3         26 return telldir $fh;
860             } elsif ($command eq 'READDIR') {
861 3 100       7 if ($context < 2) {
862 2         22 return scalar readdir $fh;
863             } else {
864 1         3 my @r = readdir $fh;
865 1         6 return @r;
866             }
867             } elsif ($command eq 'SEEKDIR') {
868 1         5 return seekdir $fh, $args->[0];
869             } elsif ($command eq 'CLOSEDIR') {
870 1         18 return closedir $fh;
871             } elsif ($command eq 'CHDIR') {
872 1         5 return chdir $fh;
873            
874             } else {
875 0         0 $@ = "tied HANDLE function '$command' not found";
876 0         0 return;
877             }
878             }
879              
880             sub process_request_CODE {
881 3     3 0 19 my ($self,$id,$command_NOTUSED,$context,$has_args,$args) = @_;
882 3         15 my $sub = $self->{obj}{$id};
883 3 50       13 if (CORE::ref($sub) eq 'threadsx::shared::code') {
884 0         0 $sub = $sub->code;
885             }
886 3 50       14 if ($context < 2) {
887 3 100       13 return scalar eval { $has_args ? $sub->(@$args) : $sub->() };
  3         48  
888             } else {
889 0 0       0 return eval { $has_args ? $sub->(@$args) : $sub->() };
  0         0  
890             }
891             }
892              
893             sub process_request_OVERLOAD {
894 36     36 0 98 my ($self,$x,$op,$args,$context) = @_;
895 36 100       119 if ($op eq '@{}') {
    100          
    50          
    100          
896 11         19 my $z = eval { \@$x };
  11         67  
897 11   50     99 $@ &&= "Not an ARRAY reference";
898 11         30 return $z;
899             } elsif ($op eq '%{}') {
900 10         19 my $z = eval { \%$x };
  10         134  
901 10   50     118 $@ &&= "Not a HASH reference";
902 10         37 return $z;
903             } elsif ($op eq '&{}') {
904 0         0 my $z = eval { \&$x };
  0         0  
905 0   0     0 $@ &&= "Not a CODE reference";
906 0         0 return $z;
907             } elsif ($op eq '${}') {
908 2         3 my $z = eval { \$$x };
  2         9  
909 2   50     14 $@ &&= "Not a SCALAR reference";
910 2         5 return $z;
911             } # elsif ($op eq '*{}') { return \*$x; }
912 13         30 my ($y,$swap) = @$args;
913 13 50       28 if ($swap) {
914 0         0 ($x,$y) = ($y,$x);
915             }
916 13         31 local $@ = '';
917 13         25 my $z;
918 13 50       52 if ($op =~ /[&|~^][.]=?/) {
919 0         0 $op =~ s/\.//;
920             }
921 13 50 33     309 if ($op eq '-X') {
    50 33        
    50 33        
    50 33        
    50 33        
    50 33        
    100 33        
    50 33        
    100 33        
    50          
922 0         0 $z = eval "-$y \$x";
923             } elsif ($op eq 'neg') {
924 0         0 $z = eval { -$x };
  0         0  
925             } elsif ($op eq '!' || $op eq '~' || $op eq '++' || $op eq '--') {
926 0         0 $z = eval "$op\$x";
927             } elsif ($op eq 'qr') {
928 0         0 $z = eval { qr/$x/ };
  0         0  
929             } elsif ($op eq 'atan2') {
930 0         0 $z = eval { atan2($x,$y) };
  0         0  
931             } elsif ($op eq 'cos' || $op eq 'sin' || $op eq 'exp' || $op eq 'abs' ||
932             $op eq 'int' || $op eq 'sqrt' || $op eq 'log') {
933 0         0 $z = eval "$op(\$x)";
934             } elsif ($op eq 'bool') {
935 4 50       10 $z = eval { $x ? 1 : 0 }; # this isn't right
  4         279  
936             } elsif ($op eq '0+') {
937 0         0 $z = eval "0 + \$x"; # this isn't right, either
938             } elsif ($op eq '""') {
939 2         5 $z = eval { "$x" };
  2         35  
940             } elsif ($op eq '<>') {
941             # always scalar context readline
942 0         0 $z = eval { readline($x) };
  0         0  
943             } else { # binary operator
944 7         584 $z = eval "\$x $op \$y";
945             }
946 13 50       663 if ($@) {
947 0         0 return;
948             }
949 13 50       28 if ($threads_avail) {
950 0         0 $z = threads::shared::shared_clone($z);
951             }
952 13         40 return $z;
953             }
954              
955             sub process_request_REF {
956 6     6 0 18 my ($self,$id,$command,$context,$has_args,$args) = @_;
957 6         16 my $obj = $self->{obj}{$id};
958 6 50       33 if (reftype($obj) ne 'REF') {
959 0         0 $@ = "Not a REF";
960 0         0 return;
961             }
962 6 50       18 if ($command eq 'deref') {
963 6         20 return $$obj;
964             }
965 0         0 $@ = "$command is not an appropriate operation for REF";
966 0         0 return;
967             }
968              
969             ########################################
970              
971             sub void_response {
972 38     38 0 79 my $addl = {};
973 38 50 33     263 if (@_ > 0 && CORE::ref($_[-1]) eq '.Patroclus') {
974 38         91 $addl = pop @_;
975             }
976 38         474 return +{ context => 0, response => undef, %$addl };
977             }
978              
979             sub scalar_response {
980 448     448 0 995 my ($self,$sides,$val) = @_;
981             return +{
982 448         2379 context => 1,
983             response => $val,
984             %$sides
985             };
986             }
987              
988             sub list_response {
989 15     15 0 61 my ($self,$sides,@val) = @_;
990             return +{
991 15         248 context => 2,
992             response => \@val,
993             %$sides
994             };
995             }
996              
997             sub error_response {
998 0     0 0 0 my ($self,@msg) = @_;
999 0         0 return { error => join('', @msg) };
1000             }
1001              
1002             ########################################
1003              
1004             sub serialize_response {
1005 568     568 0 1186 my ($self, $resp) = @_;
1006 568 100       1358 if ($resp->{context}) {
1007 463 100       998 if ($resp->{context} == 1) {
    50          
1008 448         1147 $resp->{response} = patrol($self,$resp,$resp->{response});
1009             } elsif ($resp->{context} == 2) {
1010             $resp->{response} = [
1011 15         31 map patrol($self,$resp,$_), @{$resp->{response}} ];
  15         78  
1012             }
1013             }
1014              
1015 568 100       1427 if ($resp->{out}) {
1016 12         23 $resp->{out} = [ map patrol($self,$resp,$_), @{$resp->{out}} ];
  12         57  
1017             }
1018              
1019 568         1351 sxdiag("Patro::Archy: final response before serialization: ",$resp);
1020 568         1307 $resp = Patro::LeumJelly::serialize($resp);
1021 568         25004 return $resp;
1022             }
1023              
1024             # we should not send any serialized references back to the client.
1025             # replace any references in the response with an
1026             # object id.
1027             sub patrol {
1028 545     545 0 1076 my ($self,$resp,$obj) = @_;
1029 545 50       2041 sxdiag("patrol: called on: ",defined($obj) ? "$obj" : "");
1030 545 100       1582 return $obj unless ref($obj);
1031              
1032 82 50       227 if ($threads_avail) {
1033 0 0       0 if (CORE::ref($obj) eq 'CODE') {
    0          
1034 0         0 $obj = threadsx::shared::code->new($obj);
1035 0         0 sxdiag("patrol: coderef converted");
1036             } elsif (CORE::ref($obj) eq 'GLOB') {
1037 0         0 $obj = threadsx::shared::glob->new($obj);
1038 0         0 sxdiag("patrol: glob converted");
1039             }
1040             }
1041              
1042 82         119 my $id = do {
1043 69     69   525 no overloading;
  69         133  
  69         20860  
1044 82         218 0 + $obj;
1045             };
1046              
1047 82 100       278 if (!$self->{obj}{$id}) {
1048 40         169 $self->{obj}{$id} = $obj;
1049 40         92 my $ref = CORE::ref($obj);
1050 40         82 my $reftype;
1051 40 50       149 if ($ref eq 'threadsx::shared::code') {
    50          
1052 0         0 $ref = 'CODE';
1053 0         0 $reftype = 'CODE';
1054             } elsif ($ref eq 'threadsx::shared::glob') {
1055 0         0 $ref = 'GLOB';
1056 0         0 $reftype = 'GLOB';
1057             } else {
1058 40         189 $reftype = Scalar::Util::reftype($obj);
1059             }
1060 40         194 sxdiag("patrol: ref types for $id are $ref,$reftype");
1061 40         457 $resp->{meta}{$id} = {
1062             id => $id, ref => $ref, reftype => $reftype
1063             };
1064 40 100       447 if (overload::Overloaded($obj)) {
1065 4         270 $resp->{meta}{$id}{overload} = _overloads($obj);
1066             }
1067 40         2225 sxdiag("new response meta: ",$resp->{meta}{$id});
1068             } else {
1069 42         153 sxdiag("id $id has been seen before");
1070             }
1071 82         390 return bless \$id,'.Patrobras';
1072             }
1073              
1074             sub TEST_MODE {
1075 68 50   68 0 234 if ($INC{'perl5db.pl'}) {
1076 0         0 ::xdiag("TEST_MODE adjusted for debugging");
1077 0         0 $OPTS{keep_alive} = 3600;
1078 0         0 $OPTS{fincheck_freq} = 3500;
1079 0         0 $OPTS{idle_timeout} = 3600;
1080 0         0 alarm 9999;
1081 0         0 return;
1082             }
1083 68         145 $OPTS{keep_alive} = 2;
1084 68         119 $OPTS{fincheck_freq} = 2;
1085 68         130 $OPTS{idle_timeout} = 1;
1086 68 50       226 if ($threads_avail) {
1087 0         0 $OPTS{fincheck_freq} = "0 but true";
1088             }
1089             }
1090              
1091             1;
1092              
1093             =head1 NAME
1094              
1095             Patro::Archy - remote object server for Patro
1096              
1097             =head1 VERSION
1098              
1099             0.15
1100              
1101             =head1 DESCRIPTION
1102              
1103             A server class for making references available to proxy clients
1104             in the L distribution.
1105             The server handles requests for any references that are being served,
1106             manipulates references on the server, and returns the results of
1107             operations to the proxy objects on the clients.
1108              
1109             =head1 LICENSE AND COPYRIGHT
1110              
1111             MIT License
1112              
1113             Copyright (c) 2017, Marty O'Brien
1114              
1115             Permission is hereby granted, free of charge, to any person obtaining a copy
1116             of this software and associated documentation files (the "Software"), to deal
1117             in the Software without restriction, including without limitation the rights
1118             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1119             copies of the Software, and to permit persons to whom the Software is
1120             furnished to do so, subject to the following conditions:
1121              
1122             The above copyright notice and this permission notice shall be included in all
1123             copies or substantial portions of the Software.
1124              
1125             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1126             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1127             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1128             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1129             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1130             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1131             SOFTWARE.
1132              
1133             =cut