4 our @ISA = qw(Exporter);
5 our @EXPORT = qw(initialisation_complete);
8 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
10 use POSIX qw(_exit setsid);
15 our $env_name = 'PREFORK_INTERP';
24 syslog(LOG_ERR, "$0: prefork [$$]: error: $m");
26 croak "$0: prefork: initialisation error: $m";
33 syslog(LOG_INFO, "$0 prefork [$$]: $m, quitting");
37 # Returns in the executor process
38 sub become_monitor () {
40 eval { protocol_exchange(); 1; }
41 or fail_log("protocol exchange failed: $@");
43 my $child = fork // fail_log("fork executor: $!");
46 open ::STDIN , "<& $call_fds[0]" or fail_log("dup for fd0");
47 open ::STDOUT, ">& $call_fds[1]" or fail_log("dup for fd1");
48 open ::STDERR, ">& $call_fds[2]" or fail_log("dup for fd2");
54 #---- monitor [2] ----
55 my $got = waitpid $child, 0 // fail_log("wait for executor: $!");
56 $got == $child or fail_log("wait for esecutor gave $got, expected $child");
58 protocol_write(pack "L", $?);
62 sub close_call_fds () {
69 sub protocol_write ($) {
71 return if (print CALL $d and flush CALL);
72 _exit(0) if $!==EPIPE || $!==ECONNRESET;
73 fail_log("protocol write: $!");
80 return $r if defined $r;
86 sub protocol_read_fail ($) {
88 _exit(0) if $!==ECONNRESET;
89 die("recv $what: $!");
92 sub protocol_exchange () {
93 my $greeting = "PFI\n\0\0\0\0";
94 protocol_write($greeting);
100 $r = IO::FDPass::recv(fileno(CALL));
103 protocol_read_fail("fd $_");
109 $r = read(CALL, $len, 4) // protocol_read_fail("message length");
112 $len = unpack "N", $len;
114 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
115 $r == $len or _exit(0);
117 @ARGV = split /\0/, $data, -1;
118 @ARGV >= 2 or die("message data has too few strings (".(scalar @ARGV).")");
119 length(pop(@ARGV)) and die("message data missing trailing nul");
121 while (my $s = shift @ARGV) {
123 $s =~ m/=/ or die("message data env var missing equals");
128 sub initialisation_complete {
131 # if env var not set, we're not running under prefork-interp
132 my @env_data = split / /, ($ENV{$env_name} // return);
133 croak "$env_name has too few words" unless @env_data >= 2;
134 my (@vsns) = split /,/, $env_data[0];
135 croak "$env_name doesn't offer protocol v1" unless grep { $_ eq 'v1' } @vsns;
136 my @env_fds = split /,/, $env_data[1];
137 croak "$env_name has too few fds" unless @env_fds >= 4;;
140 $num_servers = $opts{max_servers} // 4;
142 #---- setup (pm) [1] ----
145 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
147 open LISTEN, "+>&=$env_fds[0]" or croak "listen fd: $!";
148 open CALL, "+>&=$env_fds[1]" or croak "call fd: $!";
149 open WATCHE, ">+&=$env_fds[3]" or croak "watch stderr fd: $!";
151 if (!$opts{no_openlog}) {
152 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
153 $opts{log_facility} // 'log_user');
156 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
158 #---- fork for server ----
160 my $child = fork // croak "first fork failed: $!";
162 #---- setup (pm) [2], exits ----
165 setsid() > 0 or fail_log("setsid: $!");
166 # The server will be a session leader, but it won't open ttys,
169 #---- server(pm) [1] ----
171 $child = fork // croak "second fork failed: $!";
173 # we are the child, i.e. the one fa-monitor
174 local $0 = "$0 [monitor(init)]";
175 return become_monitor();
179 $children{$child} = 1;
181 # --- server(pm) [2] ----
184 open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!");
185 open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!");
186 open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");
191 my $flags = fcntl(LISTEN, F_GETFL, 0)
192 or fail_log("F_GETFL listen socket: $!");
193 $flags |= O_NONBLOCK;
194 fcntl(LISTEN, F_SETFL, $flags)
195 or fail_log("F_SETFL listen socket: $!");
199 local $0 = "$0 [server]";
204 my $full = %children >= $num_servers;
205 my $got = waitpid -1, ($full ? 0 : WNOHANG);
208 "$0 prefork [$$]: monitor process [$got] failed with wait status $?");
210 if (!exists $children{$got}) {
212 "$0 prefork [$$]: monitor process [$got] wasn't one of ours?!");
214 delete $children{$got};
218 # select for accepting or housekeeping timeout
220 vec($rbits, fileno(LISTEN), 1) = 1;
221 vec($rbits, fileno(WATCHE), 1) = 1;
223 my $nfound = select($rbits, '', $ebits, ($opts{check_interval} // 86400));
225 # Has the watcher told us to shut down, or died with a message ?
227 my $r = sysread WATCHE, $msgbuf, 2048;
230 fail_log("watcher: $msgbuf");
233 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
235 fail_log("watcher stderr read: $!");
238 # TODO stat checking, quit here if we are stale
240 # Anything to accept ?
241 if (accept(CALL, LISTEN)) {
242 $child = fork // fail_log("fork for accepted call failed: $!");
244 #---- monitor [1] ----
245 local $0 = "$0 [monitor]";
250 $children{$child} = 1;
251 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
253 syslog(LOG_WARNING, "$0 prefork [$$]: accept failed: $!");
254 if ($errcount > ($opts{max_errors} // 100)) {
256 "$0 prefork [$$]: too many accept failures, quitting");