fix indentation levels; remove dead comments and code
[scpubgit/Object-Remote.git] / lib / Object / Remote / Role / Connector / PerlInterpreter.pm
1 package Object::Remote::Role::Connector::PerlInterpreter;
2
3 use IPC::Open2;
4 use IPC::Open3; 
5 use IO::Handle;
6 use Symbol; 
7 use Object::Remote::Logging qw( :log :dlog );
8 use Object::Remote::ModuleSender;
9 use Object::Remote::Handle;
10 use Object::Remote::Future;
11 use Scalar::Util qw(blessed weaken);
12 use Moo::Role;
13
14 with 'Object::Remote::Role::Connector';
15
16 has module_sender => (is => 'lazy');
17 has ulimit => ( is => 'ro' );
18 has nice => ( is => 'ro' );
19
20 #if no child_stderr file handle is specified then stderr
21 #of the child will be connected to stderr of the parent
22 has stderr => ( is => 'rw', default => sub { undef } );
23
24 sub _build_module_sender {
25   my ($hook) =
26     grep {blessed($_) && $_->isa('Object::Remote::ModuleLoader::Hook') }
27       @INC;
28   return $hook ? $hook->sender : Object::Remote::ModuleSender->new;
29 }
30
31 has perl_command => (is => 'lazy');
32 has watchdog_timeout => ( is => 'ro', required => 1, default => sub { 0 } );
33
34 #SSH requires the entire remote command to be
35 #given as one single argument to the ssh 
36 #command line program so this jumps through
37 #some hoops
38 sub _build_perl_command {
39     my ($self) = @_; 
40     my $nice = $self->nice;
41     my $ulimit = $self->ulimit; 
42     my $shell_code = 'sh -c "';
43     
44     if (defined($ulimit)) {
45         $shell_code .= "ulimit -v $ulimit; ";
46     }
47     
48     if (defined($nice)) {
49         $shell_code .= "nice -n $nice ";
50     }
51     
52     $shell_code .= 'perl -"';
53     
54     return [ $shell_code ];        
55 }
56
57 around connect => sub {
58   my ($orig, $self) = (shift, shift);
59   my $f = $self->$start::start($orig => @_);
60   return future {
61     $f->on_done(sub {
62       my ($conn) = $f->get;
63       $self->_setup_watchdog_reset($conn); 
64       my $sub = $conn->remote_sub('Object::Remote::Logging::init_logging_forwarding');
65       $sub->('Object::Remote::Logging', Object::Remote::Logging->arg_router);
66       Object::Remote::Handle->new(
67         connection => $conn,
68         class => 'Object::Remote::ModuleLoader',
69         args => { module_sender => $self->module_sender }
70       )->disarm_free;
71       require Object::Remote::Prompt;
72       Object::Remote::Prompt::maybe_set_prompt_command_on($conn);
73     });
74     $f;
75   } 2;
76 };
77
78 sub final_perl_command { shift->perl_command }
79
80 sub _start_perl {
81   my $self = shift;
82   my $given_stderr = $self->stderr;
83   my $foreign_stderr;
84  
85   Dlog_debug { "invoking connection to perl interpreter using command line: $_" } @{$self->final_perl_command};
86     
87   if (defined($given_stderr)) {
88       #if the stderr data goes to an existing file handle
89       #an need an anonymous file handle is required
90       #as the other half of a pipe style file handle pair
91       #so the file handles can go into the run loop
92       $foreign_stderr = gensym();
93   } else {
94       #if no file handle has been specified
95       #for the child's stderr then connect
96       #the child stderr to the parent stderr
97       $foreign_stderr = ">&STDERR";
98   }
99   
100   my $pid = open3(
101     my $foreign_stdin,
102     my $foreign_stdout,
103     $foreign_stderr,
104     @{$self->final_perl_command},
105   ) or die "Failed to run perl at '$_[0]': $!";
106   
107   if (defined($given_stderr)) {   
108       Dlog_debug { "Child process STDERR is being handled via run loop" };
109         
110       Object::Remote->current_loop
111                     ->watch_io(
112                         handle => $foreign_stderr,
113                         on_read_ready => sub {
114                           my $buf = ''; 
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
120                                           ->unwatch_io(
121                                               handle => $foreign_stderr,
122                                               on_read_ready => 1
123                                             );
124                           } else {
125                               Dlog_trace { "got $len characters of stderr data for connection" };
126                               print $given_stderr $buf or die "could not send stderr data: $!";
127                           }
128                          } 
129                       );     
130   }
131       
132   return ($foreign_stdin, $foreign_stdout, $pid);
133 }
134
135 sub _open2_for {
136   my $self = shift;
137   my ($foreign_stdin, $foreign_stdout, $pid) = $self->_start_perl(@_);
138   my $to_send = $self->fatnode_text;
139   log_debug { my $len = length($to_send); "Sending contents of fat node to remote node; size is '$len' characters"  };
140   Object::Remote->current_loop
141                 ->watch_io(
142                     handle => $foreign_stdin,
143                     on_write_ready => sub {
144                       my $len = syswrite($foreign_stdin, $to_send, 32768);
145                       if (defined $len) {
146                         substr($to_send, 0, $len) = '';
147                       }
148                       # if the stdin went away, we'll never get Shere
149                       # so it's not a big deal to simply give up on !defined
150                       if (!defined($len) or 0 == length($to_send)) {
151                         log_trace { "Got EOF or error when writing fatnode data to filehandle, unwatching it" };
152                         Object::Remote->current_loop
153                                       ->unwatch_io(
154                                           handle => $foreign_stdin,
155                                           on_write_ready => 1
156                                         );
157                       } else {
158                           log_trace { "Sent $len bytes of fatnode data to remote side" };
159                       }
160                     }
161                   );
162   return ($foreign_stdin, $foreign_stdout, $pid);
163 }
164
165 sub _setup_watchdog_reset {
166     my ($self, $conn) = @_;
167     my $timer_id; 
168     
169     return unless $self->watchdog_timeout; 
170         
171     Dlog_trace { "Creating Watchdog management timer for connection id $_" } $conn->_id;
172     
173     weaken($conn);
174         
175     $timer_id = Object::Remote->current_loop->watch_time(
176         every => $self->watchdog_timeout / 3,
177         code => sub {
178             unless(defined($conn)) {
179                 log_trace { "Weak reference to connection in Watchdog was lost, terminating update timer $timer_id" };
180                 Object::Remote->current_loop->unwatch_time($timer_id);
181                 return;  
182             }
183             
184             Dlog_trace { "Reseting Watchdog for connection id $_" } $conn->_id;
185             #we do not want to block in the run loop so send the
186             #update off and ignore any result, we don't need it
187             #anyway
188             $conn->send_class_call(0, 'Object::Remote::WatchDog', 'reset');
189         }
190     );     
191 }
192
193 sub fatnode_text {
194   my ($self) = @_;
195   my $text = '';
196
197   require Object::Remote::FatNode;
198   
199   if (defined($self->watchdog_timeout)) {
200     $text = "my \$WATCHDOG_TIMEOUT = '" . $self->watchdog_timeout . "';\n";   
201     $text .= "alarm(\$WATCHDOG_TIMEOUT);\n";    
202   } else {
203       $text = "my \$WATCHDOG_TIMEOUT = undef;\n";
204   }
205   
206   $text .= 'BEGIN { $ENV{OBJECT_REMOTE_DEBUG} = 1 }'."\n"
207     if $ENV{OBJECT_REMOTE_DEBUG};
208   $text .= <<'END';
209 $INC{'Object/Remote/FatNode.pm'} = __FILE__;
210 $Object::Remote::FatNode::DATA = <<'ENDFAT';
211 END
212   $text .= do { no warnings 'once'; $Object::Remote::FatNode::DATA };
213   $text .= "ENDFAT\n";
214   $text .= <<'END';
215 eval $Object::Remote::FatNode::DATA;
216 die $@ if $@;
217 END
218   
219   $text .= "__END__\n";
220   return $text;
221 }
222
223 1;