use strict;
use warnings;
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
-use IO::Handle;
+use IO::Handle; # ->autoflush
use IO::Socket;
use POSIX qw(WNOHANG :signal_h);
use Socket qw(IPPROTO_TCP SOL_SOCKET);
STDERR->autoflush(1);
use PublicInbox::DS qw(now);
use PublicInbox::Syscall qw(SFD_NONBLOCK);
-require PublicInbox::EvCleanup;
require PublicInbox::Listener;
require PublicInbox::ParentPipe;
require PublicInbox::Sigfd;
my $worker_processes = 1;
my @listeners;
my %pids;
-my %listener_names; # sockname => IO::Handle
my %tls_opt; # scheme://sockname => args for IO::Socket::SSL->start_SSL
my $reexec_pid;
-my $cleanup;
my ($uid, $gid);
my ($default_cert, $default_key);
-END { $cleanup->() if $cleanup };
my %KNOWN_TLS = ( 443 => 'https', 563 => 'nntps' );
my %KNOWN_STARTTLS = ( 119 => 'nntp' );
sub daemon_prepare ($) {
my ($default_listen) = @_;
+ my $listener_names = {}; # sockname => IO::Handle
$oldset = POSIX::SigSet->new();
$newset = POSIX::SigSet->new();
$newset->fillset or die "fillset: $!";
if (defined $pid_file && $pid_file =~ /\.oldbin\z/) {
die "--pid-file cannot end with '.oldbin'\n";
}
- @listeners = inherit();
+ @listeners = inherit($listener_names);
# allow socket-activation users to set certs once and not
# have to configure each socket:
- my @inherited_names = keys(%listener_names) if defined($default_cert);
+ my @inherited_names = keys(%$listener_names) if defined($default_cert);
# ignore daemonize when inheriting
$daemonize = undef if scalar @listeners;
}
# TODO: use scheme to load either NNTP.pm or HTTP.pm
- next if $listener_names{$l}; # already inherited
+ next if $listener_names->{$l}; # already inherited
my (%o, $sock_pkg);
if (index($l, '/') == 0) {
$sock_pkg = 'IO::Socket::UNIX';
warn "error binding $l: $! ($@)\n" unless $s;
umask $prev;
if ($s) {
- $listener_names{sockname($s)} = $s;
+ $listener_names->{sockname($s)} = $s;
$s->blocking(0);
push @listeners, $s;
}
die "could not fork: $!\n" unless defined $pid;
exit if $pid;
}
- if (defined $pid_file) {
- write_pid($pid_file);
- my $unlink_pid = $$;
- $cleanup = sub {
- $cleanup = undef; # avoid cyclic reference
- unlink_pid_file_safe_ish($unlink_pid, $pid_file);
- };
- }
+ return unless defined $pid_file;
+
+ write_pid($pid_file);
+ # for ->DESTROY:
+ bless { pid => $$, pid_file => $pid_file }, __PACKAGE__;
}
sub worker_quit { # $_[0] = signal name or number (unused)
$@ ? ('127.0.0.1', 0) : ($host, $port);
}
-sub inherit () {
+sub inherit ($) {
+ my ($listener_names) = @_;
return () if ($ENV{LISTEN_PID} || 0) != $$;
my $fds = $ENV{LISTEN_FDS} or return ();
my $end = $fds + 2; # LISTEN_FDS_START - 1
processes when multiple service instances start.
}
- $listener_names{$k} = $s;
+ $listener_names->{$k} = $s;
push @rv, $s;
} else {
warn "failed to inherit fd=$fd (LISTEN_FDS=$fds)";
}
}
+sub master_quit ($) {
+ exit unless @listeners;
+ @listeners = ();
+ kill_workers($_[0]);
+}
+
sub master_loop {
pipe(my ($p0, $p1)) or die "failed to create parent-pipe: $!";
# 1031: F_SETPIPE_SZ, 4096: page size
fcntl($p1, 1031, 4096) if $^O eq 'linux';
my $set_workers = $worker_processes;
reopen_logs();
- my $quit = 0;
my $ignore_winch;
- my $quit_cb = sub { exit if $quit++; kill_workers($_[0]) };
my $sig = {
USR1 => sub { reopen_logs(); kill_workers($_[0]); },
USR2 => \&upgrade,
- QUIT => $quit_cb,
- INT => $quit_cb,
- TERM => $quit_cb,
+ QUIT => \&master_quit,
+ INT => \&master_quit,
+ TERM => \&master_quit,
WINCH => sub {
- return if $ignore_winch;
+ return if $ignore_winch || !@listeners;
if (-t STDIN || -t STDOUT || -t STDERR) {
$ignore_winch = 1;
warn <<EOF;
}
},
HUP => sub {
+ return unless @listeners;
$worker_processes = $set_workers;
kill_workers($_[0]);
},
TTIN => sub {
+ return unless @listeners;
if ($set_workers > $worker_processes) {
++$worker_processes;
} else {
sig_setmask($oldset) if !$sigfd;
while (1) { # main loop
my $n = scalar keys %pids;
- if ($quit) {
+ unless (@listeners) {
exit if $n == 0;
$set_workers = $worker_processes = $n = 0;
}
sub daemon_loop ($$$$) {
my ($refresh, $post_accept, $nntpd, $af_default) = @_;
- PublicInbox::EvCleanup::enable(); # early for $refresh
my %post_accept;
while (my ($k, $v) = each %tls_opt) {
if ($k =~ s!\A(?:nntps|https)://!!) {
PublicInbox::DS->SetLoopTimeout(1000);
}
PublicInbox::DS->EventLoop;
- $parent_pipe = undef;
}
-
sub run ($$$;$) {
my ($default, $refresh, $post_accept, $nntpd) = @_;
+ local $SIG{PIPE} = 'IGNORE';
daemon_prepare($default);
my $af_default = $default =~ /:8080\z/ ? 'httpready' : undef;
- daemonize();
+ my $for_destroy = daemonize();
daemon_loop($refresh, $post_accept, $nntpd, $af_default);
+ PublicInbox::DS->Reset;
+ # ->DESTROY runs when $for_destroy goes out-of-scope
}
sub do_chown ($) {
do_chown($path);
}
+sub DESTROY {
+ unlink_pid_file_safe_ish($_[0]->{pid}, $_[0]->{pid_file});
+}
+
1;