use POSIX ":sys_wait_h";
use Fcntl ':flock';
+use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
+use IO::Socket::INET;
+
use Getopt::Long;
use Time::HiRes qw (gettimeofday);
};
my $terminate_server = sub {
- my ($self) = @_;
+ my ($self, $allow_open_children) = @_;
$self->{terminate} = 1; # set flag to avoid worker restart
eval { $self->shutdown(); };
warn $@ if $@;
- # we have workers - terminate them
+ # we have workers - send TERM signal
foreach my $cpid (keys %{$self->{workers}}) {
kill(15, $cpid); # TERM childs
}
- return if $self->{got_hup_signal} &&
+ # if configured, leave children running on HUP
+ return if $allow_open_children &&
$self->{leave_children_open_on_reload};
+ # else, send TERM to old workers
+ foreach my $cpid (keys %{$self->{old_workers}}) {
+ kill(15, $cpid); # TERM childs
+ }
+
# nicely shutdown childs (give them max 10 seconds to shut down)
my $previous_alarm = alarm(10);
eval {
local $SIG{ALRM} = sub { die "timeout\n" };
while ((my $pid = waitpid (-1, 0)) > 0) {
- if (defined($self->{workers}->{$pid})) {
- delete($self->{workers}->{$pid});
- syslog('info', "worker $pid finished");
+ foreach my $id (qw(workers old_workers)) {
+ if (defined($self->{$id}->{$pid})) {
+ delete($self->{$id}->{$pid});
+ syslog('info', "worker $pid finished");
+ }
}
}
alarm(0); # avoid race condition
if ($err) {
syslog('err', "error stopping workers (will kill them now) - $err");
- foreach my $cpid (keys %{$self->{workers}}) {
- # KILL childs still alive!
- if (kill (0, $cpid)) {
- delete($self->{workers}->{$cpid});
- syslog("err", "kill worker $cpid");
- kill(9, $cpid);
- # fixme: waitpid?
+ foreach my $id (qw(workers old_workers)) {
+ foreach my $cpid (keys %{$self->{$id}}) {
+ # KILL childs still alive!
+ if (kill (0, $cpid)) {
+ delete($self->{$id}->{$cpid});
+ syslog("err", "kill worker $cpid");
+ kill(9, $cpid);
+ # fixme: waitpid?
+ }
}
}
}
local $SIG{TERM} = sub {
local ($@, $!, $?); # do not overwrite error vars
syslog('info', "received signal TERM");
- &$terminate_server($self);
+ &$terminate_server($self, 0);
&$server_cleanup($self);
&$old_sig_term(@_) if $old_sig_term;
};
local $SIG{QUIT} = sub {
local ($@, $!, $?); # do not overwrite error vars
syslog('info', "received signal QUIT");
- &$terminate_server($self);
+ &$terminate_server($self, 0);
&$server_cleanup($self);
&$old_sig_quit(@_) if $old_sig_quit;
};
local ($@, $!, $?); # do not overwrite error vars
syslog('info', "received signal INT");
$SIG{INT} = 'DEFAULT'; # allow to terminate now
- &$terminate_server($self);
+ &$terminate_server($self, 0);
&$server_cleanup($self);
&$old_sig_int(@_) if $old_sig_int;
};
syslog('info', "received signal HUP");
$self->{got_hup_signal} = 1;
if ($self->{max_workers}) {
- &$terminate_server($self);
+ &$terminate_server($self, 1);
} elsif ($self->can('hup')) {
eval { $self->hup() };
warn $@ if $@;
&$old_sig_chld(@_) if $old_sig_chld;
};
- for (;;) { # forever
+ # catch worker finished during restart phase
+ &$finish_workers($self);
+
+ # now loop forever (until we receive terminate signal)
+ for (;;) {
&$start_workers($self);
sleep(5);
&$finish_workers($self);
if ($err) {
syslog ('err', "ERROR: $err");
- # fixme: kill all workers
+ &$terminate_server($self, 1);
if (my $wait_time = $self->{restart_on_error}) {
$self->restart_daemon($wait_time);
}});
}
+# some useful helper
+
+sub create_reusable_socket {
+ my ($self, $port, $host) = @_;
+
+ die "no port specifed" if !$port;
+
+ my ($socket, $sockfd);
+
+ if (defined($sockfd = $ENV{"PVE_DAEMON_SOCKET_$port"}) &&
+ $self->{env_restart_pve_daemon}) {
+
+ die "unable to parse socket fd '$sockfd'\n"
+ if $sockfd !~ m/^(\d+)$/;
+ $sockfd = $1; # untaint
+
+ $socket = IO::Socket::INET->new;
+ $socket->fdopen($sockfd, 'w') ||
+ die "cannot fdopen file descriptor '$sockfd' - $!\n";
+
+ } else {
+
+ $socket = IO::Socket::INET->new(
+ LocalAddr => $host,
+ LocalPort => $port,
+ Listen => SOMAXCONN,
+ Proto => 'tcp',
+ ReuseAddr => 1) ||
+ die "unable to create socket - $@\n";
+
+ # we often observe delays when using Nagle algorithm,
+ # so we disable that to maximize performance
+ setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1);
+
+ $ENV{"PVE_DAEMON_SOCKET_$port"} = $socket->fileno;
+ }
+
+ # remove FD_CLOEXEC bit to reuse on exec
+ $socket->fcntl(Fcntl::F_SETFD(), 0);
+
+ return $socket;
+}
+
+
1;