2 package Proc::Prefork::Interp;
4 our @ISA = qw(Exporter);
5 our @EXPORT = qw(initialisation_complete);
10 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
12 use POSIX qw(_exit setsid :sys_wait_h :errno_h :signal_h);
13 use Sys::Syslog qw(openlog syslog LOG_INFO LOG_ERR LOG_WARNING);
17 our $env_name = 'PREFORK_INTERP';
27 syslog(LOG_ERR, "$0: prefork [$$]: error: $m");
29 carp "$0: prefork: initialisation error: $m";
36 syslog(LOG_INFO, "$0 prefork [$$]: $m, quitting");
40 # Returns in the executor process
41 sub become_monitor () {
46 # Make a process group for this call
47 setpgrp or fail_log("setpgrp failed: $!");
49 eval { protocol_exchange(); 1; }
50 or fail_log("protocol exchange failed: $@");
52 pipe EXECTERM, EXECTERMW or fail_log("pipe: $!");
54 my $child = fork // fail_log("fork executor: $!");
57 open ::STDIN , "<& $call_fds[0]" or fail_log("dup for fd0");
58 open ::STDOUT, ">& $call_fds[1]" or fail_log("dup for fd1");
59 open ::STDERR, ">& $call_fds[2]" or fail_log("dup for fd2");
67 #---- monitor [2] ----
71 vec($rbits, fileno(CALL), 1) = 1;
72 vec($rbits, fileno(EXECTERM), 1) = 1;
74 my $nfound = select($rbits, '', $ebits, undef);
77 fail_log("monitor select() failed: $!");
80 # Either the child has just died, or the caller has gone away
83 kill 'INT', 0 or fail_log("kill executor [$child]: $!");
85 my $got = waitpid $child, 0;
86 $got >= 0 // fail_log("wait for executor [$child] (2): $!");
87 $got == $child or fail_log("wait for esecutor [$child] gave [$got]");
89 protocol_write(pack "N", $?);
93 sub close_call_fds () {
100 sub protocol_write ($) {
102 return if (print CALL $d and flush CALL);
103 _exit(0) if $!==EPIPE || $!==ECONNRESET;
104 fail_log("protocol write: $!");
107 sub eintr_retry ($) {
111 return $r if defined $r;
117 sub protocol_read_fail ($) {
119 _exit(0) if $!==ECONNRESET;
120 die("recv $what: $!");
123 sub protocol_exchange () {
124 my $greeting = "PFI\n\0\0\0\0";
125 protocol_write($greeting);
131 $r = IO::FDPass::recv(fileno(CALL));
134 protocol_read_fail("fd $_");
140 my $r = read(CALL, $len, 4) // protocol_read_fail("message length");
143 $len = unpack "N", $len;
145 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
146 $r == $len or _exit(0);
148 @ARGV = split /\0/, $data, -1;
149 @ARGV >= 2 or die("message data has too few strings (".(scalar @ARGV).")");
150 length(pop(@ARGV)) and die("message data missing trailing nul");
152 while (my $s = shift @ARGV) {
154 $s =~ m/=/ or die("message data env var missing equals");
159 sub initialisation_complete {
162 # if env var not set, we're not running under prefork-interp
163 my @env_data = split / /, ($ENV{$env_name} // return);
164 croak "$env_name has too few words" unless @env_data >= 2;
165 my (@vsns) = split /,/, $env_data[0];
166 croak "$env_name doesn't specify v1" unless @vsns >= 2 && $vsns[0] eq 'v1';
167 $startup_mtime = $vsns[1];
168 my @env_fds = split /,/, $env_data[1];
169 croak "$env_name has too few fds" unless @env_fds >= 4;;
172 my $num_servers = $opts{max_servers} // 4;
174 #---- setup (pm) [1] ----
177 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
179 open LISTEN, "+>&=$env_fds[0]" or croak "listen fd: $!";
180 open CALL, "+>&=$env_fds[1]" or croak "call fd: $!";
181 open WATCHI, "+>&=$env_fds[2]" or croak "call fd: $!";
182 open WATCHE, "+>&=$env_fds[3]" or croak "watch stderr fd: $!";
184 if (!$opts{no_openlog}) {
185 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
186 $opts{log_facility} // 'log_user');
189 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
191 #---- fork for server ----
193 my $child = fork // croak "first fork failed: $!";
195 #---- setup (pm) [2], exits ----
198 setsid() > 0 or fail_log("setsid: $!");
199 # The server will be a session leader, but it won't open ttys,
202 #---- server(pm) [1] ----
204 $child = fork // croak "second fork failed: $!";
206 # we are the child, i.e. the one fa-monitor
207 local $0 = "$0 [monitor(init)]";
208 return become_monitor();
213 $children{$child} = 1;
215 # --- server(pm) [2] ----
217 local $0 = "$0 [server]";
220 open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!");
221 open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!");
222 open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");
230 my $full = %children >= $num_servers;
231 my $got = waitpid -1, ($full ? 0 : WNOHANG);
232 $got >= 0 or fail_log("failed to wait for monitor(s): $!");
234 if ($? && $? != SIGPIPE) {
236 "$0 prefork [$$]: monitor process [$got] failed with wait status $?");
238 if (!exists $children{$got}) {
240 "$0 prefork [$$]: monitor process [$got] wasn't one of ours?!");
242 delete $children{$got};
247 # select for accepting or housekeeping timeout
249 vec($rbits, fileno(LISTEN), 1) = 1;
250 vec($rbits, fileno(WATCHE), 1) = 1;
252 my $nfound = select($rbits, '', $ebits, ($opts{idle_timeout} // 1000000));
255 last if $nfound == 0;
258 fail_log("select failed: $!");
261 # Has the watcher told us to shut down, or died with a message ?
263 my $r = sysread WATCHE, $msgbuf, 2048;
266 fail_log("watcher: $msgbuf");
267 } elsif (defined $r) {
269 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
271 fail_log("watcher stderr read: $!");
274 # TODO stat checking, quit here if we are stale
276 # Anything to accept ?
277 if (accept(CALL, LISTEN)) {
278 $child = fork // fail_log("fork for accepted call failed: $!");
280 #---- monitor [1] ----
281 local $0 = "$0 [monitor]";
282 return become_monitor();
286 $children{$child} = 1;
287 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
289 syslog(LOG_WARNING, "$0 prefork [$$]: accept failed: $!");
290 if ($errcount > ($opts{max_errors} // 100)) {
291 fail_log("too many accept failures, quitting");