4 our @ISA = qw(Exporter);
5 our @EXPORT = qw(initialisation_complete);
9 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
13 our $env_name = 'PREFORK_INTERP';
20 syslog(LOG_INFO, "$0 prefork [$$]: $m, quitting");
24 # Returns in the executor process
25 sub become_monitor () {
26 my $child = fork // fail("fork executor: $!");or
29 open ::STDIN , "<& $call_fds[0]" or fail("dup for fd0");
30 open ::STDOUT, ">& $call_fds[1]" or fail("dup for fd1");
31 open ::STDERR, ">& $call_fds[2]" or fail("dup for fd2");
32 POSIX::close($_) foreach @call_fds;
37 #---- monitor [2] ----
38 my $got = waitpid $child, 0 // fail("wait for executor: $!");
39 $got == $child or fail("wait for esecutor gave $got, expected $child");
41 protocol_write(pack "L", $?);
45 sub protocol_write ($) {
47 return if (print CALL $d and flush CALL);
48 _exit(0) if $!==EPIPE || $!==ECONNRESET;
49 fail("protocol write: $!");
56 return $r if defined $r;
62 sub protocol_read_fail ($) {
64 _exit(0) if $!==ECONNRESET;
65 fail("recv $what: $!");
68 sub protocol_exchange () {
75 $r = IO::FDPass::recv(fileno(CALL));
78 protocol_read_fail("fd $_");
84 $r = read(CALL, $len, 4) // protocol_read_fail("message length");
87 $len = unpack "L", $len;
89 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
90 $r == $len or _exit(0);
92 @ARGV = split /\0/, $data;
93 @ARGV >= 2 or fail("message data has too few strings");
94 length(pop(@ARGV)) and fail("message data missing trailing nul");
96 while (my $s = shift @ARGV) {
98 $s =~ m/=/ or fail("message data env var missing equals");
103 sub initialisation_complete {
106 # if env var not set, we're not running under prefork-interp
107 my @env_params = split /,/, ($ENV{$env_name} // return), 5;
108 croak "$env_name has too few entries" unless @env_params == 5;
110 $num_servers = $opts{max_servers} // 4;
112 #---- setup (pm) [1] ----
114 $socket_path = pop @env_params;
115 foreach (@env_params) {
116 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
118 open LISTEN, "<>&=$env_params[0]" or croak "listen fd: $!";
119 open CALL, "<>&=$env_params[1]" or croak "listen fd: $!";
120 @call_fds = ($env_params[2], $env_params[3], 2);
122 if (!$opts{no_openlog}) {
123 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
124 $opts{log_facility} // 'log_user');
127 open NULL, "<>/dev/null" or croak "open /dev/null: $!";
129 #---- fork for server ----
131 my $child = fork // croak "first fork failed: $!";
133 #---- setup (pm) [2], exits ----
137 #---- server(pm) [1] ----
139 $child = fork // croak "second fork failed: $!";
141 # we are the child, i.e. the one fa-monitor
142 return become_monitor();
146 $children{$child} = 1;
148 # --- server(pm) [2] ----
150 setsid() > 0 or fail("setsid: $!");
151 open STDIN, "<&NULL" or fail("dup null onto stdin: $!");
152 open STDOUT, ">&NULL" or fail("dup null onto stdout: $!");
153 open STDERR, ">&NULL" or fail("dup null onto stderr: $!");
156 POSIX::close($_) foreach @call_fds;
159 my $flags = fcntl(LISTEN, F_GETFL, 0) or fail("F_GETFL listen socket: $!");
160 $flags |= O_NONBLOCK;
161 fcntl(LISTEN, F_SETFL, $flags) or fail("F_SETFL listen socket: $!");
168 my $full = %children >= $num_servers;
169 my $got = waitpid -1, ($full ? 0 : WNOHANG);
172 "$0 prefork [$$]: monitor process [$got] failed with wait status $?");
174 if (!exists $children{$got}) {
176 "$0 prefork [$$]: monitor process [$got] wasn't one of ours?!");
178 delete $children{$got};
182 # select for accepting or housekeeping timeout
184 vec($rbits, fileno(LISTEN), 1) = 1;
186 my $nfound = select($rbits, '', $ebits, ($opts{check_interval} // 300));
189 if (accept(CALL, LISTEN)) {
190 $child = fork // fail("fork for accepted call failed: $!");
192 #---- monitor [1] ----
195 return become_monitor();
199 $children{$child} = 1;
200 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
202 syslog(LOG_WARNING, "$0 prefork [$$]: accept failed: $!");
203 if ($errcount > ($opts{max_errors} // 100)) {
205 "$0 prefork [$$]: too many accept failures, quitting");
213 my @st_listen = stat(LISTEN) // fail("fstat listening socket: $!");
214 my @st_socket = stat($socket_path) // do {
215 if ($! == ENOENT) { server_quit("socket $socket is ENOENT"); }
216 fail("stat socket $socket: $!");
218 if ("@st_listen[0..2]" ne "@st_socket[0..2]") {
219 server_quit("socket $socket is no longer ours");