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   33955 use IPC::Open3;
  14         35245  
  14         619  
4 14     14   1844 use IO::Handle;
  14         16046  
  14         406  
5 14     14   71 use Symbol;
  14         37  
  14         593  
6 14     14   1263 use Object::Remote::Logging qw(:log :dlog router);
  14         29  
  14         91  
7 14     14   5577 use Object::Remote::ModuleSender;
  14         37  
  14         346  
8 14     14   1185 use Object::Remote::Handle;
  14         29  
  14         267  
9 14     14   65 use Object::Remote::Future;
  14         29  
  14         758  
10 14     14   97 use Scalar::Util qw(blessed weaken);
  14         25  
  14         581  
11 14     14   69 use Moo::Role;
  14         25  
  14         97  
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   7387 BEGIN { router()->exclude_forwarding; }
27              
28             sub _build_module_sender {
29             my ($hook) =
30 18 50   18   462 grep {blessed($_) && $_->isa('Object::Remote::ModuleLoader::Hook') }
  192         841  
31             @INC;
32 18 50       530 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   251 my $perl_bin = 'perl';
38              
39 22 100       90 if (exists $ENV{OBJECT_REMOTE_PERL_BIN}) {
40 1         2 $perl_bin = $ENV{OBJECT_REMOTE_PERL_BIN};
41             }
42 22         222 return [$perl_bin, '-'];
43             }
44              
45             sub _build_forward_env {
46 24     24   27400 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 705 sub final_perl_command { shift->perl_command }
75              
76             sub _start_perl {
77 20     20   46 my $self = shift;
78 20         80 my $given_stderr = $self->stderr;
79 20         33 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         159 } @{$self->final_perl_command};
  20         87  
84              
85 20 50       284 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         49 $foreign_stderr = ">&STDERR";
96             }
97              
98             my $pid = open3(
99             my $foreign_stdin,
100             my $foreign_stdout,
101             $foreign_stderr,
102 20 50       52 @{$self->final_perl_command},
  20         54  
103             ) or die "Failed to run perl at '$_[0]': $!";
104              
105 20         69492 $self->_set_pid($pid);
106              
107 20 50       179 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         507 return ($foreign_stdin, $foreign_stdout, $pid);
132             }
133              
134             sub _open2_for {
135 20     20   51 my $self = shift;
136 20         97 my ($foreign_stdin, $foreign_stdout, $pid) = $self->_start_perl(@_);
137 20         383 my $to_send = $self->fatnode_text;
138 20     0   797 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 534     534   22903 my $len = syswrite($foreign_stdin, $to_send, 32768);
144 534 50       1945 if (defined $len) {
145 534         1182 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 534 100 66     2199 if (!defined($len) or 0 == length($to_send)) {
150 19         292 log_trace { "Got EOF or error when writing fatnode data to filehandle, unwatching it" };
  0         0  
151 19         297 Object::Remote->current_loop
152             ->unwatch_io(
153             handle => $foreign_stdin,
154             on_write_ready => 1
155             );
156             } else {
157 515         3194 log_trace { "Sent $len bytes of fatnode data to remote side" };
  0         0  
158             }
159             }
160 20         843 );
161 20         363 return ($foreign_stdin, $foreign_stdout, $pid);
162             }
163              
164             sub _setup_watchdog_reset {
165 19     19   58 my ($self, $conn) = @_;
166 19         41 my $timer_id;
167              
168 19 100       117 return unless $self->watchdog_timeout;
169              
170 1     0   14 Dlog_trace { "Creating Watchdog management timer for connection id $_" } $conn->_id;
  0         0  
171              
172 1         14 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   9 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       15 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         37 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         28 $conn->send_class_call(0, 'Object::Remote::WatchDog', 'reset');
194             }
195 1         8 );
196              
197             $conn->on_close->on_ready(sub {
198 1     1   23 log_debug { "Removing watchdog for connection that is now closed" };
  0         0  
199 1         10 Object::Remote->current_loop->unwatch_time($timer_id);
200 1         17 });
201             }
202              
203             sub fatnode_text {
204 21     21 0 163 my ($self) = @_;
205 21         242 my $connection_timeout = $self->timeout;
206 21         240 my $watchdog_timeout = $self->watchdog_timeout;
207 21         216 my $text = '';
208              
209 21         10855 require Object::Remote::FatNode;
210              
211 21 50       329 if (defined($connection_timeout)) {
212 21         274 $text .= "alarm($connection_timeout);\n";
213             }
214              
215 21 100       140 if (defined($watchdog_timeout)) {
216 1         4 $text .= "my \$WATCHDOG_TIMEOUT = $watchdog_timeout;\n";
217             } else {
218 20         68 $text .= "my \$WATCHDOG_TIMEOUT = undef;\n";
219             }
220              
221 21         66 $text .= $self->_create_env_forward(@{$self->forward_env});
  21         671  
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         79 $text .= '$Object::Remote::FatNode::REMOTE_NODE = "1";' . "\n";
229              
230 21         83 $text .= <<'END';
231             $INC{'Object/Remote/FatNode.pm'} = __FILE__;
232             $Object::Remote::FatNode::DATA = <<'ENDFAT';
233             END
234 14     14   98 $text .= do { no warnings 'once'; $Object::Remote::FatNode::DATA };
  14         31  
  14         3447  
  21         45  
  21         16665  
235 21         160 $text .= "ENDFAT\n";
236 21         300 $text .= <<'END';
237             eval $Object::Remote::FatNode::DATA;
238             die $@ if $@;
239             END
240              
241 21         96 $text .= "__END__\n";
242 21         15583 return $text;
243             }
244              
245             sub _create_env_forward {
246 21     21   271 my ($self, @env_names) = @_;
247 21         164 my $code = '';
248              
249 21         138 foreach my $name (@env_names) {
250 105 100       328 next unless exists $ENV{$name};
251 21         121 my $value = $ENV{$name};
252 21         188 $name =~ s/'/\\'/g;
253 21 50       148 if(defined($value)) {
254 21         99 $value =~ s/'/\\'/g;
255 21         130 $value = "'$value'";
256             } else {
257 0         0 $value = 'undef';
258             }
259 21         161 $code .= "\$ENV{'$name'} = $value;\n";
260             }
261              
262 21         103 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