pve-manager/PVE/APIDaemon.pm
2013-04-11 08:31:03 +02:00

190 lines
3.7 KiB
Perl
Executable File

package PVE::APIDaemon;
use strict;
use warnings;
use POSIX ":sys_wait_h";
use IO::Socket::INET;
use PVE::SafeSyslog;
use PVE::HTTPServer;
my $workers = {};
sub new {
my ($this, %args) = @_;
my $class = ref($this) || $this;
die "no lockfile" if !$args{lockfile};
my $lockfh = IO::File->new(">>$args{lockfile}") ||
die "unable to open lock file '$args{lockfile}' - $!\n";
my $socket = IO::Socket::INET->new(
LocalAddr => $args{host} || undef,
LocalPort => $args{port} || 80,
Listen => SOMAXCONN,
Proto => 'tcp',
ReuseAddr => 1) ||
die "unable to create socket - $@\n";
my $cfg = { %args };
my $self = bless { cfg => $cfg }, $class;
$cfg->{socket} = $socket;
$cfg->{lockfh} = $lockfh;
$cfg->{max_workers} = 3 if !$cfg->{max_workers};
$cfg->{trusted_env} = 0 if !defined($cfg->{trusted_env});
return $self;
}
sub worker_finished {
my $cpid = shift;
syslog('info', "worker $cpid finished");
}
sub finish_workers {
local $!; local $?;
foreach my $cpid (keys %$workers) {
my $waitpid = waitpid ($cpid, WNOHANG);
if (defined($waitpid) && ($waitpid == $cpid)) {
delete ($workers->{$cpid});
worker_finished ($cpid);
}
}
}
sub test_workers {
foreach my $cpid (keys %$workers) {
if (!kill(0, $cpid)) {
waitpid($cpid, POSIX::WNOHANG());
delete $workers->{$cpid};
worker_finished ($cpid);
}
}
}
sub start_workers {
my ($self) = @_;
my $count = 0;
foreach my $cpid (keys %$workers) {
$count++;
}
my $need = $self->{cfg}->{max_workers} - $count;
return if $need <= 0;
syslog('info', "starting $need worker(s)");
while ($need > 0) {
my $pid = fork;
if (!defined ($pid)) {
syslog('err', "can't fork worker");
sleep (1);
} elsif ($pid) { #parent
$workers->{$pid} = 1;
syslog('info', "worker $pid started");
$need--;
} else {
$0 = "$0 worker";
$SIG{TERM} = $SIG{QUIT} = 'DEFAULT'; # we handle that with AnyEvent
eval {
my $server = PVE::HTTPServer->new(%{$self->{cfg}});
$server->run();
};
if (my $err = $@) {
syslog('err', $err);
sleep(5); # avoid fast restarts
}
exit (0);
}
}
}
sub terminate_server {
syslog('info', "received terminate request");
foreach my $cpid (keys %$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($workers->{$pid})) {
delete ($workers->{$pid});
worker_finished ($pid);
}
}
alarm(0); # avoid race condition
};
my $err = $@;
alarm ($previous_alarm);
if ($err) {
syslog('err', "error stopping workers (will kill them now) - $err");
foreach my $cpid (keys %$workers) {
# KILL childs still alive!
if (kill (0, $cpid)) {
delete ($workers->{$cpid});
syslog("err", "kill worker $cpid");
kill (9, $cpid);
}
}
}
}
sub start_server {
my $self = shift;
eval {
my $old_sig_chld = $SIG{CHLD};
local $SIG{CHLD} = sub {
finish_workers ();
&$old_sig_chld(@_) if $old_sig_chld;
};
my $old_sig_term = $SIG{TERM};
local $SIG{TERM} = sub {
terminate_server ();
&$old_sig_term(@_) if $old_sig_term;
};
local $SIG{QUIT} = sub {
terminate_server();
&$old_sig_term(@_) if $old_sig_term;
};
local $SIG{HUP} = sub {
syslog("info", "received reload request");
foreach my $cpid (keys %$workers) {
kill (15, $cpid); # kill childs
}
};
for (;;) { # forever
$self->start_workers();
sleep (5);
$self->test_workers();
}
};
my $err = $@;
if ($err) {
syslog('err', "ERROR: $err");
}
}
1;