4 our @ISA = qw(Exporter);
5 our @EXPORT = qw(initialisation_complete);
8 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
14 our $env_name = 'PREFORK_INTERP';
21 syslog(LOG_INFO, "$0 prefork [$$]: $m, quitting");
25 # Returns in the executor process
26 sub become_monitor () {
27 my $child = fork // fail("fork executor: $!");
30 open ::STDIN , "<& $call_fds[0]" or fail("dup for fd0");
31 open ::STDOUT, ">& $call_fds[1]" or fail("dup for fd1");
32 open ::STDERR, ">& $call_fds[2]" or fail("dup for fd2");
33 POSIX::close($_) foreach @call_fds;
38 #---- monitor [2] ----
39 my $got = waitpid $child, 0 // fail("wait for executor: $!");
40 $got == $child or fail("wait for esecutor gave $got, expected $child");
42 protocol_write(pack "L", $?);
46 sub protocol_write ($) {
48 return if (print CALL $d and flush CALL);
49 _exit(0) if $!==EPIPE || $!==ECONNRESET;
50 fail("protocol write: $!");
57 return $r if defined $r;
63 sub protocol_read_fail ($) {
65 _exit(0) if $!==ECONNRESET;
66 fail("recv $what: $!");
69 sub protocol_exchange () {
76 $r = IO::FDPass::recv(fileno(CALL));
79 protocol_read_fail("fd $_");
85 $r = read(CALL, $len, 4) // protocol_read_fail("message length");
88 $len = unpack "L", $len;
90 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
91 $r == $len or _exit(0);
93 @ARGV = split /\0/, $data;
94 @ARGV >= 2 or fail("message data has too few strings");
95 length(pop(@ARGV)) and fail("message data missing trailing nul");
97 while (my $s = shift @ARGV) {
99 $s =~ m/=/ or fail("message data env var missing equals");
104 sub initialisation_complete {
107 # if env var not set, we're not running under prefork-interp
108 my @env_params = split /,/, ($ENV{$env_name} // return), 5;
109 croak "$env_name has too few entries" unless @env_params == 5;
111 $num_servers = $opts{max_servers} // 4;
113 #---- setup (pm) [1] ----
115 $socket_path = pop @env_params;
116 foreach (@env_params) {
117 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
119 open LISTEN, "+>&=$env_params[0]" or croak "listen fd: $!";
120 open CALL, "+>&=$env_params[1]" or croak "listen fd: $!";
121 @call_fds = ($env_params[2], $env_params[3], 2);
123 if (!$opts{no_openlog}) {
124 openlog("prefork-interp $0", 'ndelay,nofatal,pid',
125 $opts{log_facility} // 'log_user');
128 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
130 #---- fork for server ----
132 my $child = fork // croak "first fork failed: $!";
134 #---- setup (pm) [2], exits ----
138 #---- server(pm) [1] ----
140 $child = fork // croak "second fork failed: $!";
142 # we are the child, i.e. the one fa-monitor
143 return become_monitor();
147 $children{$child} = 1;
149 # --- server(pm) [2] ----
151 setsid() > 0 or fail("setsid: $!");
152 open STDIN, "<&NULL" or fail("dup null onto stdin: $!");
153 open STDOUT, ">&NULL" or fail("dup null onto stdout: $!");
154 open STDERR, ">&NULL" or fail("dup null onto stderr: $!");
157 POSIX::close($_) foreach @call_fds;
160 my $flags = fcntl(LISTEN, F_GETFL, 0) or fail("F_GETFL listen socket: $!");
161 $flags |= O_NONBLOCK;
162 fcntl(LISTEN, F_SETFL, $flags) or fail("F_SETFL listen socket: $!");
169 my $full = %children >= $num_servers;
170 my $got = waitpid -1, ($full ? 0 : WNOHANG);
173 "$0 prefork [$$]: monitor process [$got] failed with wait status $?");
175 if (!exists $children{$got}) {
177 "$0 prefork [$$]: monitor process [$got] wasn't one of ours?!");
179 delete $children{$got};
183 # select for accepting or housekeeping timeout
185 vec($rbits, fileno(LISTEN), 1) = 1;
187 my $nfound = select($rbits, '', $ebits, ($opts{check_interval} // 300));
190 if (accept(CALL, LISTEN)) {
191 $child = fork // fail("fork for accepted call failed: $!");
193 #---- monitor [1] ----
196 return become_monitor();
200 $children{$child} = 1;
201 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
203 syslog(LOG_WARNING, "$0 prefork [$$]: accept failed: $!");
204 if ($errcount > ($opts{max_errors} // 100)) {
206 "$0 prefork [$$]: too many accept failures, quitting");
214 my @st_listen = stat(LISTEN) // fail("fstat listening socket: $!");
215 my @st_socket = stat($socket_path) // do {
216 if ($! == ENOENT) { server_quit("socket $socket is ENOENT"); }
217 fail("stat socket $socket: $!");
219 if ("@st_listen[0..2]" ne "@st_socket[0..2]") {
220 server_quit("socket $socket is no longer ours");