update docs so perl interpreter configuration is documented in Object::Remote::Role...
[scpubgit/Object-Remote.git] / lib / Object / Remote / Role / Connector / PerlInterpreter.pm
CommitLineData
a9fdb55e 1package Object::Remote::Role::Connector::PerlInterpreter;
2
55c0d020 3use IPC::Open3;
4c8c83d7 4use IO::Handle;
55c0d020 5use Symbol;
5add5e29 6use Object::Remote::Logging qw(:log :dlog router);
a9fdb55e 7use Object::Remote::ModuleSender;
8use Object::Remote::Handle;
fbd3b8ec 9use Object::Remote::Future;
c824fdf3 10use Scalar::Util qw(blessed weaken);
a9fdb55e 11use Moo::Role;
12
13with 'Object::Remote::Role::Connector';
14
03f41c0e 15has module_sender => (is => 'lazy');
f1d70835 16has watchdog_timeout => ( is => 'ro', required => 1, default => sub { undef });
90f5193d 17has forward_env => (is => 'ro', required => 1, builder => 1);
f129bfaf 18has perl_command => (is => 'lazy');
f1d70835 19has pid => (is => 'rwp');
20has connection_id => (is => 'rwp');
c824fdf3 21
6b7b2732 22#if no child_stderr file handle is specified then stderr
23#of the child will be connected to stderr of the parent
c824fdf3 24has stderr => ( is => 'rw', default => sub { undef } );
03f41c0e 25
5add5e29 26BEGIN { router()->exclude_forwarding; }
27
03f41c0e 28sub _build_module_sender {
18e789ab 29 my ($hook) =
30 grep {blessed($_) && $_->isa('Object::Remote::ModuleLoader::Hook') }
31 @INC;
03f41c0e 32 return $hook ? $hook->sender : Object::Remote::ModuleSender->new;
33}
34
9cd7015d 35#By policy object-remote does not invoke a shell
37efeb68 36sub _build_perl_command {
9cd7015d 37 my $perl_bin = 'perl';
55c0d020 38
9cd7015d 39 if (exists $ENV{OBJECT_REMOTE_PERL_BIN}) {
40 $perl_bin = $ENV{OBJECT_REMOTE_PERL_BIN};
8f43bcd9 41 }
aaa31f6e 42 return [$perl_bin, '-'];
37efeb68 43}
498c4ad5 44
90f5193d 45sub _build_forward_env {
46 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
03f41c0e 53around connect => sub {
54 my ($orig, $self) = (shift, shift);
fbd3b8ec 55 my $f = $self->$start::start($orig => @_);
56 return future {
57 $f->on_done(sub {
58 my ($conn) = $f->get;
f1d70835 59 $self->_setup_watchdog_reset($conn);
60 my $sub = $conn->remote_sub('Object::Remote::Logging::init_remote_logging');
f4a85080 61 $sub->('Object::Remote::Logging', router => router(), connection_id => $conn->_id);
fbd3b8ec 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;
a9fdb55e 72};
73
498c4ad5 74sub final_perl_command { shift->perl_command }
a9fdb55e 75
7efea51f 76sub _start_perl {
a9fdb55e 77 my $self = shift;
6b7b2732 78 my $given_stderr = $self->stderr;
79 my $foreign_stderr;
55c0d020 80
088218cf 81 Dlog_verbose {
82 s/\n/ /g; "invoking connection to perl interpreter using command line: $_"
83 } @{$self->final_perl_command};
55c0d020 84
6b7b2732 85 if (defined($given_stderr)) {
09130cd0 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 $foreign_stderr = gensym();
6b7b2732 91 } else {
09130cd0 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 $foreign_stderr = ">&STDERR";
6b7b2732 96 }
55c0d020 97
6b7b2732 98 my $pid = open3(
99 my $foreign_stdin,
100 my $foreign_stdout,
101 $foreign_stderr,
102 @{$self->final_perl_command},
103 ) or die "Failed to run perl at '$_[0]': $!";
55c0d020 104
f1d70835 105 $self->_set_pid($pid);
55c0d020 106
107 if (defined($given_stderr)) {
09130cd0 108 Dlog_debug { "Child process STDERR is being handled via run loop" };
55c0d020 109
09130cd0 110 Object::Remote->current_loop
111 ->watch_io(
112 handle => $foreign_stderr,
113 on_read_ready => sub {
55c0d020 114 my $buf = '';
09130cd0 115 my $len = sysread($foreign_stderr, $buf, 32768);
116 if (!defined($len) or $len == 0) {
117 log_trace { "Got EOF or error on child stderr, removing from watcher" };
118 $self->stderr(undef);
119 Object::Remote->current_loop->unwatch_io(
120 handle => $foreign_stderr,
121 on_read_ready => 1
122 );
6b7b2732 123 } else {
09130cd0 124 Dlog_trace { "got $len characters of stderr data for connection" };
125 print $given_stderr $buf or die "could not send stderr data: $!";
6b7b2732 126 }
55c0d020 127 }
128 );
6b7b2732 129 }
55c0d020 130
c824fdf3 131 return ($foreign_stdin, $foreign_stdout, $pid);
7efea51f 132}
133
134sub _open2_for {
135 my $self = shift;
136 my ($foreign_stdin, $foreign_stdout, $pid) = $self->_start_perl(@_);
fbd3b8ec 137 my $to_send = $self->fatnode_text;
f1d70835 138 log_debug { my $len = length($to_send); "Sending contents of fat node to remote node; size is '$len' characters" };
fbd3b8ec 139 Object::Remote->current_loop
140 ->watch_io(
141 handle => $foreign_stdin,
142 on_write_ready => sub {
9031635d 143 my $len = syswrite($foreign_stdin, $to_send, 32768);
fbd3b8ec 144 if (defined $len) {
145 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
353556c4 149 if (!defined($len) or 0 == length($to_send)) {
5d59cb98 150 log_trace { "Got EOF or error when writing fatnode data to filehandle, unwatching it" };
fbd3b8ec 151 Object::Remote->current_loop
152 ->unwatch_io(
153 handle => $foreign_stdin,
154 on_write_ready => 1
09130cd0 155 );
5d59cb98 156 } else {
157 log_trace { "Sent $len bytes of fatnode data to remote side" };
fbd3b8ec 158 }
159 }
160 );
a9fdb55e 161 return ($foreign_stdin, $foreign_stdout, $pid);
162}
163
c824fdf3 164sub _setup_watchdog_reset {
09130cd0 165 my ($self, $conn) = @_;
55c0d020 166 my $timer_id;
167
867e4de5 168 return unless $self->watchdog_timeout;
55c0d020 169
09130cd0 170 Dlog_trace { "Creating Watchdog management timer for connection id $_" } $conn->_id;
55c0d020 171
09130cd0 172 weaken($conn);
55c0d020 173
09130cd0 174 $timer_id = Object::Remote->current_loop->watch_time(
175 every => $self->watchdog_timeout / 3,
176 code => sub {
177 unless(defined($conn)) {
867e4de5 178 log_warn { "Weak reference to connection in Watchdog was lost, terminating update timer $timer_id" };
09130cd0 179 Object::Remote->current_loop->unwatch_time($timer_id);
867e4de5 180 return;
09130cd0 181 }
55c0d020 182
867e4de5 183 unless($conn->is_valid) {
184 log_warn { "Watchdog timer found an invalid connection, removing the timer" };
185 Object::Remote->current_loop->unwatch_time($timer_id);
186 return;
187 }
55c0d020 188
09130cd0 189 Dlog_trace { "Reseting Watchdog for connection id $_" } $conn->_id;
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 $conn->send_class_call(0, 'Object::Remote::WatchDog', 'reset');
194 }
867e4de5 195 );
55c0d020 196
867e4de5 197 $conn->on_close->on_ready(sub {
198 log_debug { "Removing watchdog for connection that is now closed" };
199 Object::Remote->current_loop->unwatch_time($timer_id);
200 });
c824fdf3 201}
202
b1cbd5be 203sub fatnode_text {
204 my ($self) = @_;
8faf2a28 205 my $connection_timeout = $self->timeout;
206 my $watchdog_timeout = $self->watchdog_timeout;
b1cbd5be 207 my $text = '';
c824fdf3 208
209 require Object::Remote::FatNode;
55c0d020 210
8faf2a28 211 if (defined($connection_timeout)) {
212 $text .= "alarm($connection_timeout);\n";
213 }
55c0d020 214
8faf2a28 215 if (defined($watchdog_timeout)) {
f1d70835 216 $text .= "my \$WATCHDOG_TIMEOUT = $watchdog_timeout;\n";
b7a853b3 217 } else {
8faf2a28 218 $text .= "my \$WATCHDOG_TIMEOUT = undef;\n";
c824fdf3 219 }
55c0d020 220
90f5193d 221 $text .= $self->_create_env_forward(@{$self->forward_env});
ffc9934e 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
f1d70835 228 $text .= '$Object::Remote::FatNode::REMOTE_NODE = "1";' . "\n";
55c0d020 229
b1cbd5be 230 $text .= <<'END';
231$INC{'Object/Remote/FatNode.pm'} = __FILE__;
232$Object::Remote::FatNode::DATA = <<'ENDFAT';
233END
fbd3b8ec 234 $text .= do { no warnings 'once'; $Object::Remote::FatNode::DATA };
b1cbd5be 235 $text .= "ENDFAT\n";
236 $text .= <<'END';
237eval $Object::Remote::FatNode::DATA;
1a2d795f 238die $@ if $@;
b1cbd5be 239END
55c0d020 240
b1cbd5be 241 $text .= "__END__\n";
242 return $text;
243}
244
90f5193d 245sub _create_env_forward {
246 my ($self, @env_names) = @_;
247 my $code = '';
248
249 foreach my $name (@env_names) {
250 next unless exists $ENV{$name};
251 my $value = $ENV{$name};
252 $name =~ s/'/\\'/g;
253 if(defined($value)) {
254 $value =~ s/'/\\'/g;
255 $value = "'$value'";
256 } else {
257 $value = 'undef';
258 }
259 $code .= "\$ENV{'$name'} = $value;\n";
260 }
261
262 return $code;
263}
264
a9fdb55e 2651;
de9062cf 266
267=head1 NAME
268
269Object::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
285This is the role that supports connections to a Perl interpreter that is executed in a
286different process. The new Perl interpreter can be either on the local or a remote machine
287and is configurable via arguments passed to the constructor.
288
289=head1 ARGUMENTS
290
291=over 4
292
293=item perl_command
294
295By default the Perl interpeter will be executed as "perl -" but this can be changed by
296providing an array reference as the value to the perl_command attribute during construction.
297
298=item stderr
299
300If this value is defined then it will be used as the file handle that receives the output
301of STDERR from the Perl interpreter process and I/O will be performed by the run loop in a
302non-blocking way. If the value is undefined then STDERR of the remote process will be connected
303directly to STDERR of the local process with out the run loop managing I/O. The default value
304is undefined.
305
306There are a few ways to use this feature. By default the behavior is to form one unified STDERR
307across all of the Perl interpreters including the local one. For small scale and quick operation
308this offers a predictable and easy to use way to get at error messages generated anywhere. If
309the local Perl interpreter crashes then the remote Perl interpreters still have an active STDERR
310and it is possible to still receive output from them. This is generally a good thing but can
311cause issues.
312
313When using a file handle as the output for STDERR once the local Perl interpreter is no longer
314running there is no longer a valid STDERR for the remote interpreters to send data to. This means
315that it is no longer possible to receive error output from the remote interpreters and that the
316shell will start to kill off the child processes. Passing a reference to STDERR for the local
317interpreter (as the SYNOPSIS shows) causes the run loop to manage I/O, one unified STDERR for
318all Perl interpreters that ends as soon as the local interpreter process does, and the shell will
319start killing children when the local interpreter exits.
320
321It is also possible to pass in a file handle that has been opened for writing. This would be
322useful for logging the output of the remote interpreter directly into a dedicated file.
323
324=item watchdog_timeout
325
326If this value is defined then it will be used as the number of seconds the watchdog will wait
327for an update before it terminates the Perl interpreter process. The default value is undefined
328and will not use the watchdog. See C<Object::Remote::Watchdog> for more information.
329
330=back
331
332=head1 SEE ALSO
333
334=over 4
335
336=item C<Object::Remote>
337
338=back
339
340=cut