2 package Proc::Prefork::Interp;
4 our @ISA = qw(Exporter);
6 prefork_initialisation_complete
7 prefork_autoreload_also_check
13 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
15 use POSIX qw(_exit setsid :sys_wait_h :errno_h :signal_h);
16 use Sys::Syslog qw(openlog syslog LOG_INFO LOG_ERR LOG_WARNING);
21 our $env_name = 'PREFORK_INTERP';
28 our @autoreload_extra_files = ();
30 sub prefork_autoreload_also_check {
31 push @autoreload_extra_files, @_;
37 syslog(LOG_ERR, "$0: prefork: error: $m");
39 carp "$0: prefork: initialisation error: $m";
46 syslog(LOG_INFO, "$0 prefork: $m, quitting");
50 # Returns in the executor process
51 sub become_monitor () {
56 # Make a process group for this call
57 setpgrp or fail_log("setpgrp failed: $!");
59 eval { protocol_exchange(); 1; }
60 or fail_log("protocol exchange failed: $@");
62 pipe EXECTERM, EXECTERMW or fail_log("pipe: $!");
64 my $child = fork // fail_log("fork executor: $!");
67 open ::STDIN , "<& $call_fds[0]" or fail_log("dup for fd0");
68 open ::STDOUT, ">& $call_fds[1]" or fail_log("dup for fd1");
69 open ::STDERR, ">& $call_fds[2]" or fail_log("dup for fd2");
77 #---- monitor [2] ----
81 vec($rbits, fileno(CALL), 1) = 1;
82 vec($rbits, fileno(EXECTERM), 1) = 1;
84 my $nfound = select($rbits, '', $ebits, undef);
87 fail_log("monitor select() failed: $!");
90 # Either the child has just died, or the caller has gone away
93 kill 'INT', 0 or fail_log("kill executor [$child]: $!");
95 my $got = waitpid $child, 0;
96 $got >= 0 // fail_log("wait for executor [$child] (2): $!");
97 $got == $child or fail_log("wait for esecutor [$child] gave [$got]");
99 protocol_write(pack "N", $?);
103 sub close_call_fds () {
104 foreach (@call_fds) {
110 sub protocol_write ($) {
112 return if (print CALL $d and flush CALL);
113 _exit(0) if $!==EPIPE || $!==ECONNRESET;
114 fail_log("protocol write: $!");
117 sub eintr_retry ($) {
121 return $r if defined $r;
127 sub protocol_read_fail ($) {
129 _exit(0) if $!==ECONNRESET;
130 die("recv $what: $!");
133 sub protocol_exchange () {
134 my $greeting = "PFI\n\0\0\0\0";
135 protocol_write($greeting);
140 $r = sysread CALL, $ibyte, 1;
142 $!==EINTR or protocol_read_fail("signalling byte");
146 $ibyte and die(sprintf "signalling byte is 0x%02x, not zero", $ibyte);
152 $r = IO::FDPass::recv(fileno(CALL));
155 protocol_read_fail("fd $_");
161 $r = read(CALL, $len, 4) // protocol_read_fail("message length");
164 $len = unpack "N", $len;
166 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
167 $r == $len or _exit(0);
169 @ARGV = split /\0/, $data, -1;
170 @ARGV >= 2 or die("message data has too few strings (".(scalar @ARGV).")");
171 length(pop(@ARGV)) and die("message data missing trailing nul");
173 while (my $s = shift @ARGV) {
175 $s =~ m/=/ or die("message data env var missing equals");
180 sub autoreload_check ($) {
182 my @s = Time::HiRes::stat($f);
184 $!==ENOENT or fail_log("autoreload check: stat failed: $f: $!");
187 if ($s[9] > $startup_mtime) {
188 syslog(LOG_INFO, "$0 prefork: reloading; due to $f");
193 sub prefork_initialisation_complete {
196 push @autoreload_extra_files, $0;
198 # if env var not set, we're not running under prefork-interp
199 my @env_data = split / /, ($ENV{$env_name} // return);
200 croak "$env_name has too few words" unless @env_data >= 2;
201 my (@vsns) = split /,/, $env_data[0];
202 croak "$env_name doesn't specify v1" unless @vsns >= 2 && $vsns[0] eq 'v1';
203 $startup_mtime = $vsns[1];
204 my @env_fds = split /,/, $env_data[1];
205 croak "$env_name has too few fds" unless @env_fds >= 4;;
208 my $num_servers = $opts{max_servers} // 4;
210 #---- setup (pm) [1] ----
213 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
215 open LISTEN, "+>&=$env_fds[0]" or croak "listen fd: $!";
216 open CALL, "+>&=$env_fds[1]" or croak "call fd: $!";
217 open WATCHI, "+>&=$env_fds[2]" or croak "call fd: $!";
218 open WATCHE, "+>&=$env_fds[3]" or croak "watch stderr fd: $!";
220 if (!$opts{no_openlog}) {
221 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
222 $opts{log_facility} // 'log_user');
225 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
227 #---- fork for server ----
229 my $child = fork // croak "first fork failed: $!";
231 #---- setup (pm) [2], exits ----
234 setsid() > 0 or fail_log("setsid: $!");
235 # The server will be a session leader, but it won't open ttys,
238 #---- server(pm) [1] ----
240 $child = fork // croak "second fork failed: $!";
242 # we are the child, i.e. the one fa-monitor
243 local $0 = "$0 [monitor(init)]";
244 return become_monitor();
249 $children{$child} = 1;
251 # --- server(pm) [2] ----
253 local $0 = "$0 [server]";
256 open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!");
257 open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!");
258 open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");
266 my $full = %children >= $num_servers;
267 my $got = waitpid -1, ($full ? 0 : WNOHANG);
268 $got >= 0 or fail_log("failed to wait for monitor(s): $!");
270 if ($? && $? != SIGPIPE) {
272 "$0 prefork: monitor process [$got] failed with wait status $?");
274 if (!exists $children{$got}) {
276 "$0 prefork: monitor process [$got] wasn't one of ours?!");
278 delete $children{$got};
283 # select for accepting or housekeeping timeout
285 vec($rbits, fileno(LISTEN), 1) = 1;
286 vec($rbits, fileno(WATCHE), 1) = 1;
288 my $nfound = select($rbits, '', $ebits, ($opts{idle_timeout} // 1000000));
291 last if $nfound == 0;
294 fail_log("select failed: $!");
297 # Has the watcher told us to shut down, or died with a message ?
299 my $r = sysread WATCHE, $msgbuf, 2048;
302 fail_log("watcher: $msgbuf");
303 } elsif (defined $r) {
305 "$0 prefork: lost socket (fresh start or cleanup?), quitting");
307 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
309 fail_log("watcher stderr read: $!");
312 if (%opts{autoreload_inc} // 1) {
313 foreach my $f (values %INC) {
314 autoreload_check($f);
317 foreach my $f (@autoreload_extra_files) {
318 autoreload_check($f);
320 foreach my $f (@{ %opts{autoreload_extra} // [] }) {
321 autoreload_check($f);
324 # Anything to accept ?
325 if (accept(CALL, LISTEN)) {
326 $child = fork // fail_log("fork for accepted call failed: $!");
328 #---- monitor [1] ----
329 $0 =~ s{ \[server\]$}{ [monitor]};
330 return become_monitor();
334 $children{$child} = 1;
335 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
337 syslog(LOG_WARNING, "$0 prefork: accept failed: $!");
338 if ($errcount > ($opts{max_errors} // 100)) {
339 fail_log("too many accept failures, quitting");