2 package Proc::Prefork::Interp;
4 our @ISA = qw(Exporter);
6 prefork_initialisation_complete
7 prefork_autoreload_also_check
13 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
15 use POSIX qw(_exit setsid :sys_wait_h :errno_h :signal_h);
16 use Sys::Syslog qw(openlog syslog LOG_INFO LOG_ERR LOG_WARNING);
21 our $env_name = 'PREFORK_INTERP';
28 our @autoreload_extra_files = ();
30 sub prefork_autoreload_also_check {
31 push @autoreload_extra_files, @_;
37 syslog(LOG_ERR, "$0: prefork: error: $m");
39 carp "$0: prefork: initialisation error: $m";
46 syslog(LOG_INFO, "$0 prefork: $m, quitting");
50 # Returns in the executor process
51 sub become_monitor () {
56 # Make a process group for this call
57 setpgrp or fail_log("setpgrp failed: $!");
59 eval { protocol_exchange(); 1; }
60 or fail_log("protocol exchange failed: $@");
62 pipe EXECTERM, EXECTERMW or fail_log("pipe: $!");
64 my $child = fork // fail_log("fork executor: $!");
67 open ::STDIN , "<& $call_fds[0]" or fail_log("dup for fd0");
68 open ::STDOUT, ">& $call_fds[1]" or fail_log("dup for fd1");
69 open ::STDERR, ">& $call_fds[2]" or fail_log("dup for fd2");
77 #---- monitor [2] ----
81 vec($rbits, fileno(CALL), 1) = 1;
82 vec($rbits, fileno(EXECTERM), 1) = 1;
84 my $nfound = select($rbits, '', $ebits, undef);
87 fail_log("monitor select() failed: $!");
90 # Either the child has just died, or the caller has gone away
93 kill 'INT', 0 or fail_log("kill executor [$child]: $!");
95 my $got = waitpid $child, 0;
96 $got >= 0 // fail_log("wait for executor [$child] (2): $!");
97 $got == $child or fail_log("wait for esecutor [$child] gave [$got]");
99 protocol_write(pack "N", $?);
103 sub close_call_fds () {
104 foreach (@call_fds) {
110 sub protocol_write ($) {
112 return if (print CALL $d and flush CALL);
113 _exit(0) if $!==EPIPE || $!==ECONNRESET;
114 fail_log("protocol write: $!");
117 sub eintr_retry ($) {
121 return $r if defined $r;
127 sub protocol_read_fail ($) {
129 _exit(0) if $!==ECONNRESET;
130 die("recv $what: $!");
133 sub protocol_exchange () {
134 my $greeting = "PFI\n\0\0\0\0";
135 protocol_write($greeting);
140 $r = sysread CALL, $ibyte, 1;
142 $!==EINTR or protocol_read_fail("signalling byte");
146 $ibyte and die(sprintf "signalling byte is 0x%02x, not zero", $ibyte);
152 $r = IO::FDPass::recv(fileno(CALL));
155 protocol_read_fail("fd $_");
161 $r = read(CALL, $len, 4) // protocol_read_fail("message length");
164 $len = unpack "N", $len;
166 $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
167 $r == $len or _exit(0);
169 @ARGV = split /\0/, $data, -1;
170 @ARGV >= 2 or die("message data has too few strings (".(scalar @ARGV).")");
171 length(pop(@ARGV)) and die("message data missing trailing nul");
173 while (my $s = shift @ARGV) {
175 $s =~ m/=/ or die("message data env var missing equals");
180 sub autoreload_check ($) {
182 my @s = Time::HiRes::stat($f);
184 $!==ENOENT or fail_log("autoreload check: stat failed: $f: $!");
187 if ($s[9] > $startup_mtime) {
188 syslog(LOG_INFO, "$0 prefork: reloading; due to $f");
193 sub prefork_initialisation_complete {
196 push @autoreload_extra_files, $0;
198 # if env var not set, we're not running under prefork-interp
199 my @env_data = split / /, ($ENV{$env_name} // return);
200 croak "$env_name has too few words" unless @env_data >= 2;
201 my (@vsns) = split /,/, $env_data[0];
202 croak "$env_name doesn't specify v1" unless @vsns >= 2 && $vsns[0] eq 'v1';
203 $startup_mtime = $vsns[1];
204 my @env_fds = split /,/, $env_data[1];
205 croak "$env_name has too few fds" unless @env_fds >= 4;;
208 my $num_servers = $opts{max_servers} // 4;
210 #---- setup (pm) [1] ----
213 $_ eq ($_+0) or croak "$env_name contains $_, not a number";
215 open LISTEN, "+>&=$env_fds[0]" or croak "listen fd: $!";
216 open CALL, "+>&=$env_fds[1]" or croak "call fd: $!";
217 open WATCHI, "+>&=$env_fds[2]" or croak "call fd: $!";
218 open WATCHE, "+>&=$env_fds[3]" or croak "watch stderr fd: $!";
220 my $log_facility = $opts{log_facility} // 'LOG_USER';
221 if (length $log_facility) {
222 openlog("prefork-interp $0", 'ndelay,nofatal,pid', $log_facility);
225 open NULL, "+>/dev/null" or croak "open /dev/null: $!";
227 #---- fork for server ----
229 my $child = fork // croak "first fork failed: $!";
231 #---- setup (pm) [2], exits ----
234 setsid() > 0 or fail_log("setsid: $!");
235 # The server will be a session leader, but it won't open ttys,
238 #---- server(pm) [1] ----
240 $child = fork // croak "second fork failed: $!";
242 # we are the child, i.e. the one fa-monitor
243 local $0 = "$0 [monitor(init)]";
244 return become_monitor();
249 $children{$child} = 1;
251 # --- server(pm) [2] ----
253 local $0 = "$0 [server]";
256 open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!");
257 open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!");
258 open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");
266 my $full = $num_servers >= 0 ? %children >= $num_servers : 0;
267 my $got = waitpid -1, ($full ? 0 : WNOHANG);
268 $got >= 0 or fail_log("failed to wait for monitor(s): $!");
270 if ($? && $? != SIGPIPE) {
272 "$0 prefork: monitor process [$got] failed with wait status $?");
274 if (!exists $children{$got}) {
276 "$0 prefork: monitor process [$got] wasn't one of ours?!");
278 delete $children{$got};
283 # select for accepting or housekeeping timeout
285 vec($rbits, fileno(LISTEN), 1) = 1;
286 vec($rbits, fileno(WATCHE), 1) = 1;
288 my $idle_timeout = $opts{idle_timeout} // 1000000;
289 $idle_timeout = undef if $idle_timeout < 0;
290 my $nfound = select($rbits, '', $ebits, $idle_timeout);
293 last if $nfound == 0;
296 fail_log("select failed: $!");
299 # Has the watcher told us to shut down, or died with a message ?
301 my $r = sysread WATCHE, $msgbuf, 2048;
304 fail_log("watcher: $msgbuf");
305 } elsif (defined $r) {
307 "$0 prefork: lost socket (fresh start or cleanup?), quitting");
309 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
311 fail_log("watcher stderr read: $!");
314 if (%opts{autoreload_inc} // 1) {
315 foreach my $f (values %INC) {
316 autoreload_check($f);
319 foreach my $f (@autoreload_extra_files) {
320 autoreload_check($f);
322 foreach my $f (@{ %opts{autoreload_extra} // [] }) {
323 autoreload_check($f);
326 # Anything to accept ?
327 if (accept(CALL, LISTEN)) {
328 $child = fork // fail_log("fork for accepted call failed: $!");
330 #---- monitor [1] ----
331 $0 =~ s{ \[server\]$}{ [monitor]};
332 return become_monitor();
336 $children{$child} = 1;
337 } elsif ($! == EINTR || $! == EAGAIN || $! == EWOULDBLOCK) {
339 syslog(LOG_WARNING, "$0 prefork: accept failed: $!");
340 if ($errcount > ($opts{max_errors} // 100)) {
341 fail_log("too many accept failures, quitting");
354 Proc::Prefork::Interp - script-side handler for prefork-interp
358 #!/usr/bin/prefork-interp -U,perl,-w
361 use Proc::Prefork::Interp;
363 ... generic initialisation code, use statements ...
365 prefork_initialisation_complete();
367 ... per-execution code ...
371 Proc::Prefork::Interp implements the script-side protocol
372 expected by the preform-interp C wrapper program.
374 The combination arranges that the startup overhead of your script
375 is paid once, and then the initialised script can service multiple
376 requests, quickly and in parallel.
378 C<prefork_initialisation_complete> actually daemonises the program,
379 forking twice, and returning in the grandchild.
381 It returns once for each associated invocation of C<prefork-interp>
382 (ie, each invocation of the script which starts C<#!/usr/bin/prefork-interp>),
383 each time in a separate process.
385 =head1 PRE-INITIALISATION STATE, CONTEXT AND ACTIONS
387 During initialisation, the program may load perl modules, and do
388 other kinds of pre-computation and pre-loading.
390 Where files are read during pre-loading, consider calling
391 C<prefork_autoreload_also_check> to arrange that the script will
392 automatically be restarted when the files change.
393 See L</"AUTOMATIC RELOADING">.
395 Before C<prefork_initialisation_complete>,
396 the script will stdin connected to /dev/null,
397 and stdout connected to its stderr.
399 It should avoid accessing its command line arguments
400 - or, at least, those which will vary from call to call.
402 Likewise it should not pay attention to environment variables
403 which are expected to change from one invocation to the next.
404 For example, if the program is a CGI script, it ought not to
405 read the CGI environment variables until after initialisation.
407 It is I<NOT> safe to open a connection to a database,
408 or other kind of server, before initialisation is complete.
409 This is because the db connection would end up being shared
410 by all of the individual executions.
412 =head1 POST-INITIALISATION STATE, CONTEXT AND ACTIONS
414 Each time C<prefork_initialisation_complete> returns,
415 corresponds to one invocation of C<prefork-interp>.
417 On return the script will have its stdin, stdout and stderr
418 connected to those provided by C<prefork-interp>'s caller
420 Likewise C<@ARGV> and C<%ENV> will have been adjusted to
421 copy the arguments and environment of the particular call.
423 By this time, the process has forked twice.
424 Its parent is not the original caller,
425 and it is in a session and a process group
426 set up for this shared script and this particular invocation,
429 Signals sent to the C<prefork-interp> will not be received
431 if C<prefork-interp> is killed, the script will receive a C<SIGINT>;
432 when that happens it ought to die promptly,
433 without doing further IO on stdin/stdout/stderr.
435 The exit status of the script will be reproduced
436 as the exit status of C<prefork-interp>,
437 so that the caller sees the right exit status.
439 =head1 DESCRIPTORS AND OTHER INHERITED PROCESS PROPERTIES
441 The per-invocation child inherits everything that is
442 set up before C<prefork_initialisation_complete>.
444 This includes ulimits, signal dispositions, uids and gids,
445 and of course file descriptors (other than 0/1/2).
447 The prefork-interp system
448 uses C<SIGINT> to terminate services when needed
449 and relies on C<SIGPIPE> to have a default disposition.
450 Do not mess with these.
452 It is not generally safe to open a connection to some kind of service
453 during initialisation.
454 Each invocation will share the socket,
455 which can cause terrible confusion (even security holes).
456 For example, do not open a database handle during initialisation.
458 =head1 AUTOMATIC RELOADING
460 The prefork-interp system supports automatic reloading/restarting,
461 when a script, or files it loads, are modified.
463 Files mentioned in C<$0> and C<%INC> will automatically be checked;
464 if any are found to be newer than the original invocation,
465 a fressh "server" will created -
466 re-running the script again from the top, as for an initial call.
468 The set of files checked in this way can be modified
469 via initialisation-complete options,
470 or by calling C<prefork_autoreload_also_check>.
472 =head1 STANDALONE OPERATION
474 A script which loads Proc::Prefork::Interp
475 and calls C<prefork_initialisation_complete>
476 can also be run standalone.
477 This can be useful for testing.
479 When not run under C<prefork-interp>, C<prefork_initialisation_complete>
480 does nothing and returns in the same process.
486 =item C<< prefork_initialisation_complete( I<%options> ) >>
488 Turns this script into a server,
489 which can be reused for subsequent invocations.
490 Returns multiple times,
491 each time in a different process,
494 When not run under C<prefork-interp>, this is a no-op.
496 C<%options> is an even-length list of options,
497 in the format used for initalising a Perl hash:
501 =item C<< max_servers => I<MAX> >>
503 Allow I<MAX> (an integer) concurrent invocations at once.
504 If too many invocations arrive at once,
505 new ones won't be served until some of them complete.
507 If I<MAX> is negative, there is no limit.
508 The limit is only applied somewhat approximately.
511 =item C<< idle_timeout => I<TIMEOUT> >>
513 If no invocations occur for this length of time, we quit;
514 future invocations would involve a restart.
516 If I<TIMEOUT> is negative, we don't time out.
518 =item C<< autoreload_inc => I<BOOL> >>
521 we don't automatically check files in C<%INC> for reloads.
522 See L</"AUTOMATIC RELOADING">.
524 =item C<< autoreload_extra => [ I<PATHS> ] >>
526 Additional paths to check for reloads
527 (as an arrayref of strings).
528 (This is in addition to paths passed to C<prefork_autoreload_also_check>.)
529 See L</"AUTOMATIC RELOADING">.
530 Default is 1 megasecond.
532 =item C<< max_errors => I<NUMBER> >>
534 If our server loop experiences more errors than this, we quit.
536 a future invocation would restart the script from the top.)
539 =item C<< log_facility => I<BOOL> >>
541 The syslog facility to use,
542 for messages from the persistent server.
544 The value is in the format expected by C<Sys::Syslog::openlog>;
545 the empty string means not to use syslog at all,
546 in which case errors experienced by the psersistent server
547 will not be reported anywhere, impeding debugging.
549 Default is C<LOG_USER>.
553 =item C<< prefork_autoreload_also_check I<PATHS> >>
555 Also check each path in I<PATHS> for being out of date;
556 if any exists and has an mtime after our setup,
557 we consider ourselves out of date and arrange for a reload.
559 It is not an error for a I<PATH> to not exist,
560 but it is an error if it can't be checked.
564 =head1 AUTHORS AND COPYRIGHT
566 The prefork-interp system was designed and implemented by Ian Jackson
567 and is distributed as part of chiark-utils.
569 prefork-interp and Proc::Prefork::Interp are
570 Copyright 2022 Ian Jackson and contributors to chiark-utils.
574 A function which works and returns in the grant parent,
575 having readjusted many important process properties,
576 is inherently rather weird.
577 Scripts using this facility must take some care.
579 Signal propagation, from caller to actual service, is lacking.
581 If the service continues to access its descriptors after receiving SIGINT,
582 the ultimate callers can experience maulfunctions
583 (eg, stolen terminal keystrokes!)
585 =head1 FUTURE POSSIBILITIES
587 This system should work for Python too.
588 I would welcome contribution of the approriate Python code.
589 Please get in touch so I can help you.
595 =item C<prefork-interp.txt>
597 Usage and options for the C<prefork-interp>
598 invocation wrapper program.
600 =item C<prefork-interp.c>
602 Design and protocol information is in the comments
603 at the top of the source file.