File Coverage

blib/lib/Patro.pm
Criterion Covered Total %
statement 172 203 84.7
branch 59 88 67.0
condition 6 11 54.5
subroutine 34 39 87.1
pod 5 7 71.4
total 276 348 79.3


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