File Coverage

blib/lib/Patro.pm
Criterion Covered Total %
statement 131 157 83.4
branch 42 70 60.0
condition 4 8 50.0
subroutine 26 30 86.6
pod 5 7 71.4
total 208 272 76.4


line stmt bran cond sub pod time code
1             package Patro;
2 60     60   2847362 use strict;
  60         146  
  60         1615  
3 60     60   287 use warnings;
  60         103  
  60         1436  
4 60     60   14894 use Patro::LeumJelly;
  60         141  
  60         1714  
5 60     60   365 use Scalar::Util;
  60         102  
  60         2284  
6 60     60   307 use Data::Dumper;
  60         96  
  60         2126  
7 60     60   19813 use Socket ();
  60         159509  
  60         1549  
8 60     60   351 use Carp;
  60         110  
  60         2930  
9 60     60   306 use base 'Exporter';
  60         96  
  60         15080  
10             our @EXPORT = qw(patronize getProxies);
11              
12             our $VERSION = '0.13';
13              
14             BEGIN {
15 60 50   60   302 if (defined &CORE::read) {
16             *CORE::GLOBAL::read = sub (*\$$;$) {
17 4     4   1115 $Patro::read_sysread_flag = 'read';
18 4 50       76 goto &CORE::read if defined &CORE::read;
19 60         249 };
20             *CORE::GLOBAL::sysread = sub (*\$$;$) {
21 2     2   764 $Patro::read_sysread_flag = 'sysread';
22 2 50       56 goto &CORE::sysread if defined &CORE::sysread;
23 60         175 };
24             } else {
25 0         0 $Patro::read_sysread_flag = 'read?';
26             }
27 60         150 *CORE::GLOBAL::ref = \&Patro::ref;
28 60         114 *CORE::GLOBAL::truncate = \&Patro::_truncate;
29 60         147 *CORE::GLOBAL::stat = \&Patro::_stat;
30 60         129 *CORE::GLOBAL::flock = \&Patro::_flock;
31 60         75115 *CORE::GLOBAL::fcntl = \&Patro::_fcntl;
32             }
33              
34             sub import {
35 60     60   516 my ($class, @args) = @_;
36 60         345 my @tags = grep /^:/, @args;
37 60         194 @args = grep !/^:/, @args;
38 60         96 $Patro::SECURE = 1;
39 60         140 foreach my $tag (@tags) {
40 68 100       246 if ($tag eq ':test') {
41 59         20932 require Patro::Server;
42 59         375 Patro::Server->TEST_MODE;
43              
44             # some tests will check if the remote object has changed
45             # after being manipulated by the proxy. This can only
46             # happen with a threaded server (or with certain objects
47             # that do not maintain state in local memory), so we should
48             # skip those tests if we are using the forked server.
49             *ok_threaded = sub {
50 9 50   9   465 if ($Patro::Server::threads_avail) {
51 0         0 goto &Test::More::ok;
52             } else {
53 9 50       66 Test::More::ok(1, $_[1] ? "$_[1] - SKIPPED" :
54             "skip test that requires threaded server");
55             }
56 59         242 };
57             # a poor man's Data::Dumper, but works for Patro::N objects.
58             *xjoin = sub {
59 15     15   37 join(",", map { my $r = $_;
  37         58  
60 37   100     59 my $rt = Patro::reftype($_) || "";
61             $rt eq 'ARRAY' ? "[" . xjoin(@$r) . "]" :
62 37 50       171 $rt eq 'HASH' ? do {
    100          
63 0         0 "{".xjoin(map{"$_:'".$r->{$_}."'"}sort keys %$r)."}"
  0         0  
64             } : $_ } @_)
65 59         342 };
66 59         171 push @EXPORT, 'ok_threaded', 'xjoin';
67             }
68 68 100       260 if ($tag eq ':insecure') {
69 3         6 $Patro::SECURE = 0;
70             }
71             }
72              
73 60 50 33     283 if (defined($ENV{PATRO_THREADS}) &&
74             !$ENV{PATRO_THREADS}) {
75 0         0 $INC{'threads.pm'} = 1;
76             }
77 60     60   20197 eval "use threads;1";
  0         0  
  0         0  
  60         2815  
78 60     60   16762 eval "use threadsx::shared";
  60         150  
  60         290  
  60         10733  
79 60         170 $Patro::Server::threads_avail = $threads::threads;
80 60 50       192 if (!defined &threads::tid) {
81 60     2   280 *threads::tid = sub { 0 };
  2         186  
82             }
83 60 50 33     258 if ($ENV{PATRO_THREADS} && !$Patro::Server::threads_avail) {
84 0         0 warn "Threaded Patro server was requested but was not available\n";
85             }
86 60         7222 Patro->export_to_level(1, 'Patro', @args, @EXPORT);
87             }
88              
89             # make Patro::nize a synonym for patronize
90 0     0 0 0 sub nize { goto &patronize }
91              
92             sub patronize {
93 59 50   59 1 2067 croak 'usage: Patro::patronize(@refs)' if @_ == 0;
94 59         352 require Patro::Server;
95 59         473 my $server = Patro::Server->new({}, @_);
96 18         441 return $server->{config};
97             }
98              
99             sub ref (_) {
100 3621 50   3621 1 325459 my $obj = @_ ? $_[0] : $_;
101 3621         5953 my $ref = CORE::ref($obj);
102 3621 100       8358 if (!Patro::LeumJelly::isProxyRef($ref)) {
103 3598         10184 return $ref;
104             }
105 23         85 my $handle = Patro::LeumJelly::handle($obj);
106 23         80 return _fetch($handle, "ref");
107             }
108              
109             sub reftype {
110 50     50 1 1270 my $ref = CORE::ref($_[0]);
111 50 100       127 if (!Patro::LeumJelly::isProxyRef($ref)) {
112 33         124 return Scalar::Util::reftype($_[0]);
113             }
114 17         55 my $handle = Patro::LeumJelly::handle($_[0]);
115 17         52 return _fetch($handle, "reftype");
116             }
117              
118             sub _allrefs {
119 0     0   0 return (CORE::ref($_[0]), Patro::ref($_[0]),
120             Scalar::Util::reftype($_[0]), Patro::reftype($_[0]));
121             }
122              
123             sub client {
124 9 50   9 1 424 if (!Patro::LeumJelly::isProxyRef(CORE::ref($_[0]))) {
125 0         0 return; # not a remote proxy object
126             }
127 9         41 return _fetch(Patro::LeumJelly::handle($_[0]),"client");
128             }
129              
130             sub _fetch {
131             # _fetch HASH, LIST
132             # where HASH is an object that overloads the '%{}'
133             # operator, temporarily unbless it, fetch values for
134             # one or more keys, and restore the original blessing.
135             # Returns the retrieved values.
136            
137 526     526   1578 my ($hash, @keys) = @_;
138 526         885 my $ref = CORE::ref($hash);
139 526         681 my @r;
140 526 50       914 if (!$ref) {
141 0         0 @r = @{$hash}{@keys};
  0         0  
142             } else {
143 526         893 bless $hash, '###';
144 526         688 @r = @{$hash}{@keys};
  526         1358  
145 526         965 bless $hash, $ref;
146             }
147 526 50       2061 return wantarray ? @r : @r > 0 ? $r[-1] : undef;
    100          
148             }
149              
150             sub main::xdiag {
151 0     0   0 my @lt = localtime;
152 0         0 my $lt = sprintf "%02d:%02d:%02d", @lt[2,1,0];
153 0         0 my $pid = $$;
154 0 0       0 $pid .= "-" . threads->tid if $threads::threads;
155 0 0       0 my @msg = map { CORE::ref($_)
  0 0       0  
156             ? CORE::ref($_) =~ /^Patro::N/
157             ? "<" . CORE::ref($_) . ">"
158             : Data::Dumper::Dumper($_) : $_ } @_;
159 0 0       0 if ($INC{'Test/More.pm'}) {
160 0         0 Test::More::diag("xdiag $pid $lt: ",@msg);
161             } else {
162 0         0 print STDERR "xdiag $pid $lt: @msg\n";
163             }
164             }
165              
166             # Patro OO-interface
167              
168             sub new {
169 23     23 0 11013 my ($pkg,$config) = @_;
170              
171             # want config to be a Patro::Config
172             # but it could be a string or a filenae (from Patro::Config::to_string
173             # or to_file)
174 23 100       116 if (!CORE::ref($config)) {
175 11 100       271 if (-f $config) {
176 3         16 $config = Patro::Config->from_file($config);
177             } else {
178 8         53 $config = Patro::Config->from_string($config);
179             }
180             }
181              
182 23 50       137 croak __PACKAGE__,": no host" unless $config->{host};
183 23 50       105 croak __PACKAGE__,": no port" unless $config->{port};
184              
185 23         1395 my $iaddr = Socket::inet_aton($config->{host});
186 23         460 my $paddr = Socket::pack_sockaddr_in($config->{port}, $iaddr);
187              
188 23 50       1485 socket(my $socket, Socket::PF_INET(), Socket::SOCK_STREAM(),
189             getprotobyname("tcp")) or croak __PACKAGE__,": socket $!";
190 23 50       3277 connect($socket,$paddr)
191             or croak(__PACKAGE__, ": connect to $config->{host}:$config->{port}",
192             " failed: $!");
193              
194 23         492 my $self = bless {
195             config => $config,
196             socket => $socket,
197             proxies => {},
198             objs => [],
199             }, $pkg;
200              
201 23         137 $Patro::SERVER_VERSION = $config->{version};
202              
203 23         117 my $fh0 = select $socket;
204 23         129 $| = 1;
205 23         125 select $fh0;
206              
207 23         54 foreach my $odata (@{$config->{store}}) {
  23         127  
208 35         250 my $proxyref = Patro::LeumJelly::getproxy($odata,$self);
209 35         88 $self->{proxies}{$odata->{id}} = $proxyref;
210 35         49 push @{$self->{objs}}, $proxyref;
  35         98  
211             }
212 23         171 return $self;
213             }
214              
215             sub getProxies {
216 23     23 1 66 my $patro = shift;
217 23 100       118 if (CORE::ref($patro) ne 'Patro') {
218 4         57 $patro = Patro->new($patro);
219             }
220 23 100       105 return wantarray ? @{$patro->{objs}} : $patro->{objs}[0];
  16         70  
221             }
222              
223             ########################################
224              
225             sub _truncate {
226 2     2   372 my ($fh,$len) = @_;
227 2 100       13 if (CORE::ref($fh) eq 'Patro::N5') {
228 1         4 return $fh->_tied->__('TRUNCATE',1,$len);
229             } else {
230 1         74 return CORE::truncate($fh,$len);
231             }
232             }
233              
234             sub _fcntl {
235 0     0   0 my ($fh,$func,$scalar) = @_;
236 0 0       0 if (CORE::ref($fh) eq 'Patro::N5') {
237 0         0 return $fh->_tied->__('FCNTL',1,$func,$scalar);
238             } else {
239 0         0 return CORE::fcntl($fh,$func,$scalar);
240             }
241             }
242              
243             sub _stat {
244 4     4   736 my ($fh) = @_;
245 4 100       17 if (CORE::ref($fh) eq 'Patro::N5') {
246 2         5 my $context = defined(wantarray) + wantarray + 0;
247 2         7 return $fh->_tied->__('STAT',$context);
248             } else {
249 2         41 return CORE::stat $fh;
250             }
251             }
252              
253             1;
254              
255             =head1 NAME
256              
257             Patro - proxy access to remote objects
258              
259              
260             =head1 VERSION
261              
262             0.13
263              
264              
265             =head1 SYNOPSIS
266              
267             # on machine 1 (server)
268             use Patro;
269             my $obj = ...
270             $config = patronize($obj);
271             $config->to_file( 'config_file' );
272              
273              
274             # on machines 2 through n (clients)
275             use Patro;
276             my ($proxy) = Patro->new( 'config_file' )->getProxies;
277             ...
278             $proxy->{key} = $val; # updates $obj->{key} for obj on server
279             $val = $proxy->method(@args); # calls $obj->method for obj on server
280              
281              
282             =head1 DESCRIPTION
283              
284             C is a mechanism for making any Perl reference in one Perl program
285             accessible is other processes, even processes running on different hosts.
286             The "proxy" references have the same look and feel as the native references
287             in the original process, and any manipulation of the proxy reference
288             will have an effect on the original reference.
289              
290             =head2 Some important features:
291              
292             =over 4
293              
294             =item * Hash members and array elements
295              
296             Accessing or updating hash values or array values on a remote reference
297             is done with the same syntax as with the local reference:
298              
299             # host 1
300             use Patro;
301             my $hash1 = { abc => 123, def => [ 456, { ghi => "jkl" }, "mno" ] };
302             my $config = patronize($hash1);
303             ...
304              
305             # host 2
306             use Patro;
307             my $hash2 = Patro->new($config)->getProxies;
308             print $hash2->{abc}; # "123"
309             $hash2->{def}[2] = "pqr"; # updates $hash1 on host 1
310             print delete $hash2->{def}[1]{ghi}; # "jkl", updates $hash1 on host1
311              
312             =item * Remote method calls
313              
314             Method calls on the proxy object are propagated to the original object,
315             affecting the remote object and returning the result of the call.
316              
317             # host 1
318             use Patro;
319             sub Foofie::new { bless \$_[1],'Foofie' }
320             sub Foofie::blerp { my $self=shift; wantarray ? (5,6,7,$$self) : ++$$self }
321             patronize(Foofie->new(17))->to_file('/config/file');
322             ...
323              
324             # host 2
325             use Patro;
326             my $foo = Patro->new('/config/file')->getProxies;
327             my @x = $foo->blerp; # (5,6,7,17)
328             my $x = $foo->blerp; # 18
329              
330             =item * Overloaded operators
331              
332             Any overloaded operations on the original object are supported on the
333             remote object.
334              
335             # host 1
336             use Patro;
337             my $obj = Barfie->new(2,5);
338             $config = patronize($obj);
339             $config->to_file( 'config' );
340             package Barfie;
341             use overload '+=' => sub { $_ += $_[1] for @{$_[0]->{vals}};$_[0] },
342             fallback => 1;
343             sub new {
344             my $pkg = shift;
345             bless { vals => [ @_ ] }, $pkg;
346             }
347             sub prod { my $self = shift; my $z=1; $z*=$_ for @{$_[0]->{vals}}; $z }
348              
349             # host 2
350             use Patro;
351             my $proxy = getProxies('config');
352             print $proxy->prod; # calls Barfie::prod($obj) on host1, 2 * 5 => 10
353             $proxy += 4; # calls Barfie '+=' sub on host1
354             print $proxy->prod; # 6 * 9 => 54
355              
356             =item * Code references
357              
358             Patro supports sharing code references and data structures that contain
359             code references (think dispatch tables). Proxies to these code references
360             can invoke the code, which will then run on the server.
361              
362             # host 1
363             use Patro;
364             my $foo = sub { $_[0] + 42 };
365             my $d = {
366             f1 => sub { $_[0] + $_[1] },
367             f2 => sub { $_[0] * $_[1] },
368             f3 => sub { int( $_[0] / ($_[1] || 1) ) },
369             g1 => sub { $_[0] += $_[1]; 18 },
370             };
371             patronize($foo,$d)->to_file('config');
372             ...
373              
374             # host 2
375             use Patro;
376             my ($p_foo, $p_d) = getProxies('config');
377             print $p_foo->(17); # "59" (42+17)
378             print $p_d->{f1}->(7,33); # "40" (7+33)
379             print $p_d->{f3}->(33,7); # "4" int(33/7)
380             ($x,$y) = (5,6);
381             $p_d->{g1}->($x,$y);
382             print $x; # "11" ($x:6 += 5)
383              
384             =item * filehandles
385              
386             Filehandles can also be shared through the Patro framework.
387              
388             # host 1
389             use Patro;
390             open my $fh, '>', 'host1.log';
391             patronize($fh)->to_file('config');
392             ...
393              
394             # host 2
395             use Patro;
396             my $ph = getProxies('config');
397             print $ph "A log message for the server\n";
398              
399             Calling C through a proxy filehandle presents some security concerns.
400             A client could read or write any file on the server host visible to the
401             server's user id. Or worse, a client could open a pipe through the handle
402             to run an arbitrary command on the server. C and C operations
403             on proxy filehandles will not be allowed unless the process running the
404             Patro server imports C with the C<:insecure> tag.
405              
406             =back
407              
408              
409             =head1 FUNCTIONS
410              
411             =head2 patronize
412              
413             CONFIG = patronize(@REFS)
414              
415             Creates a server on the local machine that provides proxy access to
416             the given list of references. It returns an object
417             with information about how to connect to the server.
418              
419             The returned object has C and C methods
420             to store the configuration where it can be read by other processes.
421             Either the object, its string representation, or the filename
422             containing config information may be used as input to the
423             L<"getProxies"> function to retrieve proxies to the shared
424             references.
425              
426             =head2 getProxies
427              
428             PROXIES = getProxies(CONFIG)
429             PROXIES = getProxies(STRING)
430             PROXIES = getProxies(FILENAME)
431              
432             Connects to a server on another machine, specified in the C
433             string, and returns proxies to the list of references that are served.
434             In scalar context, returns a proxy to the first reference that is
435             served.
436              
437             See the L<"PROXIES"> section below for what you can do with the output
438             of this function.
439              
440             =head2 ref
441              
442             TYPE = Patro::ref(PROXY)
443              
444             For the given proxy object, returns the ref type of the remote object
445             being served. If the input is not a proxy, returns C.
446             See also L<"reftype">.
447              
448             =head2 reftype
449              
450             TYPE = Patro::reftype(PROXY)
451              
452             Returns the simple reference type (e.g., C) of the remote
453             object associated with the given proxy, as if we were calling
454             C on the remote object. Returns C if
455             the input is not a proxy object.
456              
457             =head2 client
458              
459             CLIENT = Patro::client(PROXY)
460              
461             Returns the IPC client object used by the given proxy to communicate
462             with the remote object server. The client object contains information
463             about how to communicate with the server and other connection
464             configuration.
465              
466              
467             =head1 PROXIES
468              
469             Proxy references, as returned by the L<"getProxies"> function above,
470             or sometimes returned in other calls to the server, are designed
471             to look and feel as much as possible as the real references on the
472             remote server that they provide access to, so any operation or
473             expression with the proxy on the local machine should evaluate
474             to the same value(s) as the same operation or expression with the
475             real object/reference on the remote server.
476             When the server if using threads and is sharing the served
477             objects between threads, an update to the
478             proxy object will affect the remote object, and vice versa.
479              
480             =head2 Example 1: network file synchronization
481              
482             Network file systems are notoriously flaky when it comes to
483             synchronizing files that are being written to by processes on
484             many different hosts [citation needed]. C provides a
485             workaround, in that every machine can hold to a proxy to an object
486             that writes to a file, with the object running on a single machine.
487              
488             # master
489             package SyncWriter;
490             use Fcntl qw(:flock SEEK_END);
491             sub new {
492             my ($pkg,$filename) = @_;
493             open my $fh, '>', $filename;
494             bless { fh => $fh }, $pkg;
495             }
496             sub print {
497             my $self = shift;
498             flock $self->{fh}, LOCK_EX;
499             seek $self->{fh}, 0, SEEK_END;
500             print {$self->{fh}} @_;
501             flock $self->{fh}, LOCK_UN;
502             }
503             sub printf { ... }
504              
505             use Patro;
506             my $writer = SyncWriter->new("file.log");
507             my $cfg = patronize($writer);
508             open my $fh,'>','/network/accessible/file';
509             print $fh $cfg;
510             close $fh;
511             ...
512              
513             # slaves
514             use Patro;
515             open my $fh, '<', '/network/accessible/file';
516             my $cfg = <$fh>;
517             close $fh;
518             my $writer = Patro->new($cfg)->getProxies;
519             ...
520             # $writer->print with a proxy $writer
521             # invokes $writer->print on the host. Since all
522             # the file operations are done on a single machine,
523             # there are no network synchronization issues
524             $writer->print("a log message\n");
525             ...
526              
527             =head2 Example 2: Distributed queue
528              
529             A program that distributes tasks to several threads or several
530             child processes can be extended to distribute tasks to
531             several machines.
532              
533             # master
534             use Patro;
535             my $queue = [ 'job 1', 'job 2', ... ];
536             patronize($queue)->to_file('/network/accessible/file');
537             ...
538              
539             # slaves
540             use Patro;
541             my $queue = Patro->new('/network/accessible/file')->getProxies;
542              
543             while (my $task = shift @$queue) {
544             ... do task ...
545             }
546              
547             (This example will not work without threads. For a more robust
548             network-safe queue that will run with forks, see L)
549              
550             =head2 Example 3: Keep your code secret
551              
552             If you distribute your Perl code for others to use, it is very
553             difficult to keep others from being able to see (and potentially
554             steal) your code. L are penetrable by
555             any determined reverse engineer. Most other suggestions for keeping
556             your code secret revolve around running your code on a server,
557             and having your clients send input and receive output through a
558             network service.
559              
560             The C framework can make this service model easier to use.
561             Design a small set of objects that can execute your code, provide
562             your clients with a public API for those objects, and make proxies
563             to your objects available through C.
564              
565             # code to run on client machine
566             use Patro;
567             my $cfg = ... # provided by you
568             my ($obj1,$obj2) = Patro->new($cfg)->getProxies;
569             $result = $obj1->method($arg1,$arg2);
570             ...
571              
572             In this model, the client can use the objects and methods of your code,
573             and inspect the members of your objects through the proxies, but the
574             client cannot see the source code.
575              
576              
577             =head1 ENVIRONMENT
578              
579             C pays attention to the following environment variables.
580              
581             =head2 PATRO_THREADS
582              
583             If the environment variable C is set, C will use
584             it to determine whether to use a forked server or a threaded server
585             to provide proxy access to objects. If this variable is not set,
586             C will use threads if the L module can be loaded.
587              
588              
589             =head1 LIMITATIONS
590              
591             The C<-X> file test operations on a proxy filehandle depend on
592             the file test implementation in L, which is available
593             only in Perl v5.12 or better.
594              
595             When the server uses forks (because threads are unavailable or
596             because L<"PATRO_THREADS"> was set to a false value), it is less
597             practical to share variables between processes.
598             When you manipulate a proxy reference, you are
599             manipulating the copy of the reference running in a different process
600             than the remote server. So you will not observe a change in the
601             reference on the server (unless you use a class that does not save
602             state in local memory, like L).
603              
604              
605             =head1 DOCUMENTATION AND SUPPORT
606              
607             Up-to-date (blead version) sources for C are on github at
608             L
609              
610             You can find documentation for this module with the perldoc command.
611              
612             perldoc Patro
613              
614             You can also look for information at:
615              
616             =over 4
617              
618             =item * RT: CPAN's request tracker
619              
620             Report bugs and request missing features at
621             L
622              
623             =item * AnnoCPAN: Annotated CPAN documentation
624              
625             L
626              
627             =item * CPAN Ratings
628              
629             L
630              
631             =item * Search CPAN
632              
633             L
634              
635             =back
636              
637              
638             =head1 LICENSE AND COPYRIGHT
639              
640             MIT License
641              
642             Copyright (c) 2017, Marty O'Brien
643              
644             Permission is hereby granted, free of charge, to any person obtaining a copy
645             of this software and associated documentation files (the "Software"), to deal
646             in the Software without restriction, including without limitation the rights
647             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
648             copies of the Software, and to permit persons to whom the Software is
649             furnished to do so, subject to the following conditions:
650              
651             The above copyright notice and this permission notice shall be included in all
652             copies or substantial portions of the Software.
653              
654             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
655             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
656             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
657             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
658             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
659             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
660             SOFTWARE.
661              
662             =cut