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';
26 syslog(LOG_ERR, "$0: prefork [$$]: error: $m");
28 carp "$0: prefork: initialisation error: $m";
35 syslog(LOG_INFO, "$0 prefork [$$]: $m, quitting");
39 # Returns in the executor process
40 sub become_monitor () {
45 # Make a process group for this call
46 setpgrp or fail_log("setpgrp failed: $!");
48 eval { protocol_exchange(); 1; }
49 or fail_log("protocol exchange failed: $@");
51 pipe EXECTERM, EXECTERMW or fail_log("pipe: $!");
53 my $child = fork // fail_log("fork executor: $!");
56 open ::STDIN , "<& $call_fds[0]" or fail_log("dup for fd0");
57 open ::STDOUT, ">& $call_fds[1]" or fail_log("dup for fd1");
58 open ::STDERR, ">& $call_fds[2]" or fail_log("dup for fd2");
66 #---- monitor [2] ----
70 vec($rbits, fileno(CALL), 1) = 1;
71 vec($rbits, fileno(EXECTERM), 1) = 1;
73 my $nfound = select($rbits, '', $ebits, undef);
76 fail_log("monitor select() failed: $!");
79 # Either the child has just died, or the caller has gone away
82 kill 'INT', 0 or fail_log("kill executor [$child]: $!");
84 my $got = waitpid $child, 0;
85 $got >= 0 // fail_log("wait for executor [$child] (2): $!");
86 $got == $child or fail_log("wait for esecutor [$child] gave [$got]");
88 protocol_write(pack "N", $?);
92 sub close_call_fds () {
99 sub protocol_write ($) {
101 return if (print CALL $d and flush CALL);
102 _exit(0) if $!==EPIPE || $!==ECONNRESET;
103 fail_log("protocol write: $!");
106 sub eintr_retry ($) {
110 return $r if defined $r;
116 sub protocol_read_fail ($) {
118 _exit(0) if $!==ECONNRESET;
119 die("recv $what: $!");
122 sub protocol_exchange () {
123 my $greeting = "PFI\n\0\0\0\0";
124 protocol_write($greeting);
130 $r = IO::FDPass::recv(fileno(CALL));
133 protocol_read_fail("fd $_");
139 my $r = read(CALL, $len, 4) // protocol_read_fail("message length");
142 $len = unpack "N", $len;
144 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
145 $r == $len or _exit(0);
147 @ARGV = split /\0/, $data, -1;
148 @ARGV >= 2 or die("message data has too few strings (".(scalar @ARGV).")");
149 length(pop(@ARGV)) and die("message data missing trailing nul");
151 while (my $s = shift @ARGV) {
153 $s =~ m/=/ or die("message data env var missing equals");
158 sub initialisation_complete {
161 # if env var not set, we're not running under prefork-interp
162 my @env_data = split / /, ($ENV{$env_name} // return);
163 croak "$env_name has too few words" unless @env_data >= 2;
164 my (@vsns) = split /,/, $env_data[0];
165 croak "$env_name doesn't offer protocol v1" unless grep { $_ eq 'v1' } @vsns;
166 my @env_fds = split /,/, $env_data[1];
167 croak "$env_name has too few fds" unless @env_fds >= 4;;
170 my $num_servers = $opts{max_servers} // 4;
172 #---- setup (pm) [1] ----
175 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
177 open LISTEN, "+>&=$env_fds[0]" or croak "listen fd: $!";
178 open CALL, "+>&=$env_fds[1]" or croak "call fd: $!";
179 open WATCHI, "+>&=$env_fds[2]" or croak "call fd: $!";
180 open WATCHE, "+>&=$env_fds[3]" or croak "watch stderr fd: $!";
182 if (!$opts{no_openlog}) {
183 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
184 $opts{log_facility} // 'log_user');
187 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
189 #---- fork for server ----
191 my $child = fork // croak "first fork failed: $!";
193 #---- setup (pm) [2], exits ----
196 setsid() > 0 or fail_log("setsid: $!");
197 # The server will be a session leader, but it won't open ttys,
200 #---- server(pm) [1] ----
202 $child = fork // croak "second fork failed: $!";
204 # we are the child, i.e. the one fa-monitor
205 local $0 = "$0 [monitor(init)]";
206 return become_monitor();
211 $children{$child} = 1;
213 # --- server(pm) [2] ----
215 local $0 = "$0 [server]";
218 open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!");
219 open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!");
220 open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");
228 my $full = %children >= $num_servers;
229 my $got = waitpid -1, ($full ? 0 : WNOHANG);
230 $got >= 0 or fail_log("failed to wait for monitor(s): $!");
232 if ($? && $? != SIGPIPE) {
234 "$0 prefork [$$]: monitor process [$got] failed with wait status $?");
236 if (!exists $children{$got}) {
238 "$0 prefork [$$]: monitor process [$got] wasn't one of ours?!");
240 delete $children{$got};
245 # select for accepting or housekeeping timeout
247 vec($rbits, fileno(LISTEN), 1) = 1;
248 vec($rbits, fileno(WATCHE), 1) = 1;
250 my $nfound = select($rbits, '', $ebits, ($opts{idle_timeout} // 1000000));
253 last if $nfound == 0;
256 fail_log("select failed: $!");
259 # Has the watcher told us to shut down, or died with a message ?
261 my $r = sysread WATCHE, $msgbuf, 2048;
264 fail_log("watcher: $msgbuf");
265 } elsif (defined $r) {
267 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
269 fail_log("watcher stderr read: $!");
272 # TODO stat checking, quit here if we are stale
274 # Anything to accept ?
275 if (accept(CALL, LISTEN)) {
276 $child = fork // fail_log("fork for accepted call failed: $!");
278 #---- monitor [1] ----
279 local $0 = "$0 [monitor]";
280 return become_monitor();
284 $children{$child} = 1;
285 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
287 syslog(LOG_WARNING, "$0 prefork [$$]: accept failed: $!");
288 if ($errcount > ($opts{max_errors} // 100)) {
289 fail_log("too many accept failures, quitting");