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;