File Coverage

blib/lib/Object/Remote/Role/Connector/PerlInterpreter.pm
Criterion Covered Total %
statement 109 141 77.3
branch 20 34 58.8
condition 2 6 33.3
subroutine 23 28 82.1
pod 0 2 0.0
total 154 211 72.9


line stmt bran cond sub pod time code
1             package Object::Remote::Role::Connector::PerlInterpreter;
2              
3 14     14   29567 use IPC::Open3;
  14         31164  
  14         622  
4 14     14   1859 use IO::Handle;
  14         13729  
  14         421  
5 14     14   51 use Symbol;
  14         17  
  14         570  
6 14     14   1084 use Object::Remote::Logging qw(:log :dlog router);
  14         21  
  14         99  
7 14     14   7178 use Object::Remote::ModuleSender;
  14         29  
  14         323  
8 14     14   1003 use Object::Remote::Handle;
  14         18  
  14         260  
9 14     14   48 use Object::Remote::Future;
  14         14  
  14         783  
10 14     14   57 use Scalar::Util qw(blessed weaken);
  14         23  
  14         570  
11 14     14   56 use Moo::Role;
  14         21  
  14         136  
12              
13             with 'Object::Remote::Role::Connector';
14              
15             has module_sender => (is => 'lazy');
16             has watchdog_timeout => ( is => 'ro', required => 1, default => sub { undef });
17             has forward_env => (is => 'ro', required => 1, builder => 1);
18             has perl_command => (is => 'lazy');
19             has pid => (is => 'rwp');
20             has connection_id => (is => 'rwp');
21              
22             #if no child_stderr file handle is specified then stderr
23             #of the child will be connected to stderr of the parent
24             has stderr => ( is => 'rw', default => sub { undef } );
25              
26 14     14   4949 BEGIN { router()->exclude_forwarding; }
27              
28             sub _build_module_sender {
29             my ($hook) =
30 18 50   18   4925 grep {blessed($_) && $_->isa('Object::Remote::ModuleLoader::Hook') }
  192         470  
31             @INC;
32 18 50       393 return $hook ? $hook->sender : Object::Remote::ModuleSender->new;
33             }
34              
35             #By policy object-remote does not invoke a shell
36             sub _build_perl_command {
37 22     22   4206 my $perl_bin = 'perl';
38              
39 22 100       90 if (exists $ENV{OBJECT_REMOTE_PERL_BIN}) {
40 1         3 $perl_bin = $ENV{OBJECT_REMOTE_PERL_BIN};
41             }
42 22         142 return [$perl_bin, '-'];
43             }
44              
45             sub _build_forward_env {
46 24     24   19777 return [qw(
47             OBJECT_REMOTE_PERL_BIN
48             OBJECT_REMOTE_LOG_LEVEL OBJECT_REMOTE_LOG_FORMAT OBJECT_REMOTE_LOG_SELECTIONS
49             OBJECT_REMOTE_LOG_FORWARDING
50             )];
51             }
52              
53             around connect => sub {
54             my ($orig, $self) = (shift, shift);
55             my $f = $self->$start::start($orig => @_);
56             return future {
57             $f->on_done(sub {
58             my ($conn) = $f->get;
59             $self->_setup_watchdog_reset($conn);
60             my $sub = $conn->remote_sub('Object::Remote::Logging::init_remote_logging');
61             $sub->('Object::Remote::Logging', router => router(), connection_id => $conn->_id);
62             Object::Remote::Handle->new(
63             connection => $conn,
64             class => 'Object::Remote::ModuleLoader',
65             args => { module_sender => $self->module_sender }
66             )->disarm_free;
67             require Object::Remote::Prompt;
68             Object::Remote::Prompt::maybe_set_prompt_command_on($conn);
69             });
70             $f;
71             } 2;
72             };
73              
74 42     42 0 152 sub final_perl_command { shift->perl_command }
75              
76             sub _start_perl {
77 20     20   31 my $self = shift;
78 20         57 my $given_stderr = $self->stderr;
79 20         24 my $foreign_stderr;
80              
81             Dlog_verbose {
82 0     0   0 s/\n/ /g; "invoking connection to perl interpreter using command line: $_"
  0         0  
83 20         98 } @{$self->final_perl_command};
  20         61  
84              
85 20 50       267 if (defined($given_stderr)) {
86             #if the stderr data goes to an existing file handle
87             #an anonymous file handle is required
88             #as the other half of a pipe style file handle pair
89             #so the file handles can go into the run loop
90 0         0 $foreign_stderr = gensym();
91             } else {
92             #if no file handle has been specified
93             #for the child's stderr then connect
94             #the child stderr to the parent stderr
95 20         41 $foreign_stderr = ">&STDERR";
96             }
97              
98             my $pid = open3(
99             my $foreign_stdin,
100             my $foreign_stdout,
101             $foreign_stderr,
102 20 50       26 @{$self->final_perl_command},
  20         47  
103             ) or die "Failed to run perl at '$_[0]': $!";
104              
105 20         74689 $self->_set_pid($pid);
106              
107 20 50       122 if (defined($given_stderr)) {
108 0     0   0 Dlog_debug { "Child process STDERR is being handled via run loop" };
  0         0  
109              
110             Object::Remote->current_loop
111             ->watch_io(
112             handle => $foreign_stderr,
113             on_read_ready => sub {
114 0     0   0 my $buf = '';
115 0         0 my $len = sysread($foreign_stderr, $buf, 32768);
116 0 0 0     0 if (!defined($len) or $len == 0) {
117 0         0 log_trace { "Got EOF or error on child stderr, removing from watcher" };
  0         0  
118 0         0 $self->stderr(undef);
119 0         0 Object::Remote->current_loop->unwatch_io(
120             handle => $foreign_stderr,
121             on_read_ready => 1
122             );
123             } else {
124 0         0 Dlog_trace { "got $len characters of stderr data for connection" };
  0         0  
125 0 0       0 print $given_stderr $buf or die "could not send stderr data: $!";
126             }
127             }
128 0         0 );
129             }
130              
131 20         179 return ($foreign_stdin, $foreign_stdout, $pid);
132             }
133              
134             sub _open2_for {
135 20     20   30 my $self = shift;
136 20         88 my ($foreign_stdin, $foreign_stdout, $pid) = $self->_start_perl(@_);
137 20         361 my $to_send = $self->fatnode_text;
138 20     0   480 log_debug { my $len = length($to_send); "Sending contents of fat node to remote node; size is '$len' characters" };
  0         0  
  0         0  
139             Object::Remote->current_loop
140             ->watch_io(
141             handle => $foreign_stdin,
142             on_write_ready => sub {
143 515     515   144610 my $len = syswrite($foreign_stdin, $to_send, 32768);
144 515 50       1483 if (defined $len) {
145 515         1195 substr($to_send, 0, $len) = '';
146             }
147             # if the stdin went away, we'll never get Shere
148             # so it's not a big deal to simply give up on !defined
149 515 100 66     2599 if (!defined($len) or 0 == length($to_send)) {
150 19         312 log_trace { "Got EOF or error when writing fatnode data to filehandle, unwatching it" };
  0         0  
151 19         385 Object::Remote->current_loop
152             ->unwatch_io(
153             handle => $foreign_stdin,
154             on_write_ready => 1
155             );
156             } else {
157 496         3435 log_trace { "Sent $len bytes of fatnode data to remote side" };
  0         0  
158             }
159             }
160 20         605 );
161 20         153 return ($foreign_stdin, $foreign_stdout, $pid);
162             }
163              
164             sub _setup_watchdog_reset {
165 19     19   38 my ($self, $conn) = @_;
166 19         31 my $timer_id;
167              
168 19 100       114 return unless $self->watchdog_timeout;
169              
170 1     0   9 Dlog_trace { "Creating Watchdog management timer for connection id $_" } $conn->_id;
  0         0  
171              
172 1         11 weaken($conn);
173              
174             $timer_id = Object::Remote->current_loop->watch_time(
175             every => $self->watchdog_timeout / 3,
176             code => sub {
177 2 50   2   8 unless(defined($conn)) {
178 0         0 log_warn { "Weak reference to connection in Watchdog was lost, terminating update timer $timer_id" };
  0         0  
179 0         0 Object::Remote->current_loop->unwatch_time($timer_id);
180 0         0 return;
181             }
182              
183 2 50       11 unless($conn->is_valid) {
184 0         0 log_warn { "Watchdog timer found an invalid connection, removing the timer" };
  0         0  
185 0         0 Object::Remote->current_loop->unwatch_time($timer_id);
186 0         0 return;
187             }
188              
189 2         28 Dlog_trace { "Reseting Watchdog for connection id $_" } $conn->_id;
  0         0  
190             #we do not want to block in the run loop so send the
191             #update off and ignore any result, we don't need it
192             #anyway
193 2         30 $conn->send_class_call(0, 'Object::Remote::WatchDog', 'reset');
194             }
195 1         7 );
196              
197             $conn->on_close->on_ready(sub {
198 1     1   18 log_debug { "Removing watchdog for connection that is now closed" };
  0         0  
199 1         12 Object::Remote->current_loop->unwatch_time($timer_id);
200 1         4 });
201             }
202              
203             sub fatnode_text {
204 21     21 0 62 my ($self) = @_;
205 21         127 my $connection_timeout = $self->timeout;
206 21         105 my $watchdog_timeout = $self->watchdog_timeout;
207 21         134 my $text = '';
208              
209 21         8365 require Object::Remote::FatNode;
210              
211 21 50       157 if (defined($connection_timeout)) {
212 21         160 $text .= "alarm($connection_timeout);\n";
213             }
214              
215 21 100       91 if (defined($watchdog_timeout)) {
216 1         5 $text .= "my \$WATCHDOG_TIMEOUT = $watchdog_timeout;\n";
217             } else {
218 20         45 $text .= "my \$WATCHDOG_TIMEOUT = undef;\n";
219             }
220              
221 21         40 $text .= $self->_create_env_forward(@{$self->forward_env});
  21         258  
222              
223             #Action at a distance but at least it's not spooky - the logging
224             #system needs to know if a node is remote but there is a period
225             #during init where the remote connection information has not been
226             #setup on the remote side yet so this flag allows a graceful
227             #degredation to happen
228 21         62 $text .= '$Object::Remote::FatNode::REMOTE_NODE = "1";' . "\n";
229              
230 21         43 $text .= <<'END';
231             $INC{'Object/Remote/FatNode.pm'} = __FILE__;
232             $Object::Remote::FatNode::DATA = <<'ENDFAT';
233             END
234 14     14   71 $text .= do { no warnings 'once'; $Object::Remote::FatNode::DATA };
  14         19  
  14         2904  
  21         37  
  21         11805  
235 21         267 $text .= "ENDFAT\n";
236 21         94 $text .= <<'END';
237             eval $Object::Remote::FatNode::DATA;
238             die $@ if $@;
239             END
240              
241 21         46 $text .= "__END__\n";
242 21         10447 return $text;
243             }
244              
245             sub _create_env_forward {
246 21     21   132 my ($self, @env_names) = @_;
247 21         80 my $code = '';
248              
249 21         81 foreach my $name (@env_names) {
250 105 100       282 next unless exists $ENV{$name};
251 21         80 my $value = $ENV{$name};
252 21         89 $name =~ s/'/\\'/g;
253 21 50       75 if(defined($value)) {
254 21         63 $value =~ s/'/\\'/g;
255 21         63 $value = "'$value'";
256             } else {
257 0         0 $value = 'undef';
258             }
259 21         111 $code .= "\$ENV{'$name'} = $value;\n";
260             }
261              
262 21         72 return $code;
263             }
264              
265             1;
266              
267             =head1 NAME
268              
269             Object::Remote::Role::Connector::PerlInterpreter - Role for connections to a Perl interpreter
270              
271             =head1 SYNOPSIS
272              
273             use Object::Remote;
274              
275             my %opts = (
276             perl_command => [qw(nice -n 10 perl -)],
277             watchdog_timeout => 120, stderr => \*STDERR,
278             );
279              
280             my $local_connection = Object::Remote->connect('-', %opts);
281             my $hostname = Sys::Hostname->can::on($remote, 'hostname');
282              
283             =head1 DESCRIPTION
284              
285             This is the role that supports connections to a Perl interpreter that is executed in a
286             different process. The new Perl interpreter can be either on the local or a remote machine
287             and is configurable via arguments passed to the constructor.
288              
289             =head1 ARGUMENTS
290              
291             =over 4
292              
293             =item perl_command
294              
295             By default the Perl interpeter will be executed as "perl -" but this can be changed by
296             providing an array reference as the value to the perl_command attribute during construction.
297              
298             =item stderr
299              
300             If this value is defined then it will be used as the file handle that receives the output
301             of STDERR from the Perl interpreter process and I/O will be performed by the run loop in a
302             non-blocking way. If the value is undefined then STDERR of the remote process will be connected
303             directly to STDERR of the local process with out the run loop managing I/O. The default value
304             is undefined.
305              
306             There are a few ways to use this feature. By default the behavior is to form one unified STDERR
307             across all of the Perl interpreters including the local one. For small scale and quick operation
308             this offers a predictable and easy to use way to get at error messages generated anywhere. If
309             the local Perl interpreter crashes then the remote Perl interpreters still have an active STDERR
310             and it is possible to still receive output from them. This is generally a good thing but can
311             cause issues.
312              
313             When using a file handle as the output for STDERR once the local Perl interpreter is no longer
314             running there is no longer a valid STDERR for the remote interpreters to send data to. This means
315             that it is no longer possible to receive error output from the remote interpreters and that the
316             shell will start to kill off the child processes. Passing a reference to STDERR for the local
317             interpreter (as the SYNOPSIS shows) causes the run loop to manage I/O, one unified STDERR for
318             all Perl interpreters that ends as soon as the local interpreter process does, and the shell will
319             start killing children when the local interpreter exits.
320              
321             It is also possible to pass in a file handle that has been opened for writing. This would be
322             useful for logging the output of the remote interpreter directly into a dedicated file.
323              
324             =item watchdog_timeout
325              
326             If this value is defined then it will be used as the number of seconds the watchdog will wait
327             for an update before it terminates the Perl interpreter process. The default value is undefined
328             and will not use the watchdog. See C for more information.
329              
330             =back
331              
332             =head1 SEE ALSO
333              
334             =over 4
335              
336             =item C
337              
338             =back
339              
340             =cut