X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=data%2FPVE%2FDaemon.pm;h=838d59673203e3c084f28213eef719ab753022f7;hp=752522940aa6170cf1bcf45e5380d6a93b5da3d7;hb=88260370407565f5b91882f8345aff807130bcf7;hpb=a8ba2293d48b39a1530b004bb618bb99260e4e7d diff --git a/data/PVE/Daemon.pm b/data/PVE/Daemon.pm index 7525229..838d596 100644 --- a/data/PVE/Daemon.pm +++ b/data/PVE/Daemon.pm @@ -5,11 +5,14 @@ package PVE::Daemon; # Features: # * lock and write PID file /var/run/$name.pid to make sure onyl # one instance is running. +# * keep lock open during restart # * correctly daemonize (redirect STDIN/STDOUT) # * restart by stop/start, exec, or signal HUP # * daemon restart on error (option 'restart_on_error') # * handle worker processes (option 'max_workers') - +# * allow to restart while workers are still runningl +# (option 'leave_children_open_on_reload') + use strict; use warnings; use PVE::SafeSyslog; @@ -22,15 +25,6 @@ use Time::HiRes qw (gettimeofday); use base qw(PVE::CLIHandler); -$SIG{'__WARN__'} = sub { - my $err = $@; - my $t = $_[0]; - chomp $t; - print "$t\n"; - syslog('warning', "WARNING: %s", $t); - $@ = $err; -}; - $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; my $daemon_initialized = 0; # we only allow one instance @@ -44,6 +38,13 @@ my $close_daemon_lock = sub { delete $self->{daemon_lock_fh}; }; +my $log_err = sub { + my ($msg) = @_; + chomp $msg; + print STDERR "$msg\n"; + syslog('err', "%s", $msg); +}; + # call this if you fork() from child # Note: we already call this for workers, so it is only required # if you fork inside a simple daemon (max_workers == 0). @@ -67,23 +68,37 @@ my $lockpidfile = sub { my $lkfn = $self->{pidfile} . ".lock"; - $self->{daemon_lock_fh} = IO::File->new(">>$lkfn"); + my $waittime = 0; + + if (my $fd = $self->{env_pve_lock_fd}) { + + $self->{daemon_lock_fh} = IO::Handle->new_from_fd($fd, "a"); + + } else { + + $waittime = 5; + $self->{daemon_lock_fh} = IO::File->new(">>$lkfn"); + } + if (!$self->{daemon_lock_fh}) { - my $msg = "can't aquire lock on file '$lkfn' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; + die "can't open lock '$lkfn' - $!\n"; } - for (my $i = 0; $i < 5; $i ++) { + for (my $i = 0; $i < $waittime; $i ++) { return if flock ($self->{daemon_lock_fh}, LOCK_EX|LOCK_NB); sleep(1); } if (!flock ($self->{daemon_lock_fh}, LOCK_EX|LOCK_NB)) { &$close_daemon_lock($self); - my $msg = "can't aquire lock '$lkfn' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; + my $err = $!; + + my ($running, $pid) = $self->running(); + if ($running) { + die "can't aquire lock '$lkfn' - daemon already started (pid = $pid)\n"; + } else { + die "can't aquire lock '$lkfn' - $err\n"; + } } }; @@ -92,11 +107,8 @@ my $writepidfile = sub { my $pidfile = $self->{pidfile}; - if (!open (PIDFH, ">$pidfile")) { - my $msg = "can't open pid file '$pidfile' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; - } + die "can't open pid file '$pidfile' - $!\n" if !open (PIDFH, ">$pidfile"); + print PIDFH "$$\n"; close (PIDFH); }; @@ -111,11 +123,13 @@ my $server_cleanup = sub { my $finish_workers = sub { my ($self) = @_; - foreach my $cpid (keys %{$self->{workers}}) { - my $waitpid = waitpid($cpid, WNOHANG); - if (defined($waitpid) && ($waitpid == $cpid)) { - delete ($self->{workers}->{$cpid}); - syslog('info', "worker $cpid finished"); + foreach my $id (qw(workers old_workers)) { + foreach my $cpid (keys %{$self->{$id}}) { + my $waitpid = waitpid($cpid, WNOHANG); + if (defined($waitpid) && ($waitpid == $cpid)) { + delete ($self->{$id}->{$cpid}); + syslog('info', "worker $cpid finished"); + } } } }; @@ -183,6 +197,9 @@ my $terminate_server = sub { kill(15, $cpid); # TERM childs } + return if $self->{got_hup_signal} && + $self->{leave_children_open_on_reload}; + # nicely shutdown childs (give them max 10 seconds to shut down) my $previous_alarm = alarm(10); eval { @@ -217,8 +234,14 @@ my $terminate_server = sub { my $server_run = sub { my ($self, $debug) = @_; + # fixme: handle restart lockfd &$lockpidfile($self); + # remove FD_CLOEXEC bit to reuse on exec + $self->{daemon_lock_fh}->fcntl(Fcntl::F_SETFD(), 0); + + $ENV{PVE_DAEMON_LOCK_FD} = $self->{daemon_lock_fh}->fileno; + # run in background my $spid; @@ -235,25 +258,22 @@ my $server_run = sub { PVE::INotify::inotify_close(); $spid = fork(); if (!defined ($spid)) { - my $msg = "can't put server into background - fork failed"; - syslog('err', $msg); - die "ERROR: $msg\n"; + die "can't put server into background - fork failed"; } elsif ($spid) { # parent exit (0); } PVE::INotify::inotify_init(); } - &$writepidfile($self); - - POSIX::setsid(); - if ($self->{env_restart_pve_daemon}) { syslog('info' , "restarting server"); } else { + &$writepidfile($self); syslog('info' , "starting server"); } + POSIX::setsid(); + open STDERR, '>&STDOUT' || die "can't close STDERR\n"; my $old_sig_term = $SIG{TERM}; @@ -287,9 +307,9 @@ my $server_run = sub { $SIG{HUP} = sub { local ($@, $!, $?); # do not overwrite error vars syslog('info', "received signal HUP"); + $self->{got_hup_signal} = 1; if ($self->{max_workers}) { &$terminate_server($self); - $self->{got_hup_signal} = 1; } elsif ($self->can('hup')) { eval { $self->hup() }; warn $@ if $@; @@ -321,7 +341,7 @@ my $server_run = sub { if ($err) { syslog ('err', "ERROR: $err"); - # fixme: kill all workers + &$terminate_server($self); if (my $wait_time = $self->{restart_on_error}) { $self->restart_daemon($wait_time); @@ -340,57 +360,88 @@ my $server_run = sub { sub new { my ($this, $name, $cmdline, %params) = @_; - my $restart = $ENV{RESTART_PVE_DAEMON}; + $name = 'daemon' if !$name; # should not happen - delete $ENV{RESTART_PVE_DAEMON}; + initlog($name); - die "please run as root\n" if !$restart && ($> != 0); + my $self; - die "missing name" if !$name; + eval { - die "can't create more that one PVE::Daemon" if $daemon_initialized; - $daemon_initialized = 1; + my $restart = $ENV{RESTART_PVE_DAEMON}; + delete $ENV{RESTART_PVE_DAEMON}; - PVE::INotify::inotify_init(); + my $lockfd = $ENV{PVE_DAEMON_LOCK_FD}; + delete $ENV{PVE_DAEMON_LOCK_FD}; - initlog($name); + if (defined($lockfd)) { + die "unable to parse lock fd '$lockfd'\n" + if $lockfd !~ m/^(\d+)$/; + $lockfd = $1; # untaint + } - my $class = ref($this) || $this; - - my $self = bless { - name => $name, - run_dir => '/var/run', - env_restart_pve_daemon => $restart, - workers => {}, - }, $class; - - foreach my $opt (keys %params) { - my $value = $params{$opt}; - if ($opt eq 'restart_on_error') { - $self->{$opt} = $value; - } elsif ($opt eq 'stop_wait_time') { - $self->{$opt} = $value; - } elsif ($opt eq 'run_dir') { - $self->{$opt} = $value; - } elsif ($opt eq 'max_workers') { - $self->{$opt} = $value; - } else { - die "unknown option '$opt'"; + die "please run as root\n" if !$restart && ($> != 0); + + die "can't create more that one PVE::Daemon" if $daemon_initialized; + $daemon_initialized = 1; + + PVE::INotify::inotify_init(); + + my $class = ref($this) || $this; + + $self = bless { + name => $name, + run_dir => '/var/run', + env_restart_pve_daemon => $restart, + env_pve_lock_fd => $lockfd, + workers => {}, + old_workers => {}, + }, $class; + + foreach my $opt (keys %params) { + my $value = $params{$opt}; + if ($opt eq 'restart_on_error') { + $self->{$opt} = $value; + } elsif ($opt eq 'stop_wait_time') { + $self->{$opt} = $value; + } elsif ($opt eq 'run_dir') { + $self->{$opt} = $value; + } elsif ($opt eq 'max_workers') { + $self->{$opt} = $value; + } elsif ($opt eq 'leave_children_open_on_reload') { + $self->{$opt} = $value; + } else { + die "unknown daemon option '$opt'\n"; + } + } + + if ($restart && $self->{max_workers}) { + if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) { + foreach my $pid (split(':', $wpids)) { + if ($pid =~ m/^(\d+)$/) { + $self->{old_workers}->{$1} = 1; + } + } + } } - } - $self->{pidfile} = "$self->{run_dir}/${name}.pid"; + $self->{pidfile} = "$self->{run_dir}/${name}.pid"; - $self->{nodename} = PVE::INotify::nodename(); + $self->{nodename} = PVE::INotify::nodename(); - $self->{cmdline} = []; + $self->{cmdline} = []; - foreach my $el (@$cmdline) { - $el =~ m/^(.*)$/; # untaint - push @{$self->{cmdline}}, $1; - } + foreach my $el (@$cmdline) { + $el =~ m/^(.*)$/; # untaint + push @{$self->{cmdline}}, $1; + } - $0 = $name; + $0 = $name; + }; + if (my $err = $@) { + &$log_err($err); + exit(-1); + } return $self; } @@ -412,6 +463,12 @@ sub restart_daemon { $ENV{RESTART_PVE_DAEMON} = 1; + if ($self->{max_workers}) { + my @workers = keys %{$self->{workers}}; + push @workers, keys %{$self->{old_workers}}; + $ENV{PVE_DAEMON_WORKER_PIDS} = join(':', @workers); + } + sleep($waittime) if $waittime; # avoid high server load due to restarts PVE::INotify::inotify_close(); @@ -460,7 +517,11 @@ sub run { sub start { my ($self, $debug) = @_; - &$server_run($self, $debug); + eval { &$server_run($self, $debug); }; + if (my $err = $@) { + &$log_err("start failed - $err"); + exit(-1); + } } my $read_pid = sub { @@ -516,14 +577,21 @@ sub stop { } if (-f $self->{pidfile}) { - # try to get the lock - &$lockpidfile($self); - &$server_cleanup($self); + eval { + # try to get the lock + &$lockpidfile($self); + &$server_cleanup($self); + }; + if (my $err = $@) { + &$log_err("cleanup failed - $err"); + } } } sub register_start_command { - my ($self, $class, $description) = @_; + my ($self, $description) = @_; + + my $class = ref($self); $class->register_method({ name => 'start', @@ -574,7 +642,9 @@ my $reload_daemon = sub { }; sub register_restart_command { - my ($self, $class, $use_hup, $description) = @_; + my ($self, $use_hup, $description) = @_; + + my $class = ref($self); $class->register_method({ name => 'restart', @@ -597,7 +667,9 @@ sub register_restart_command { } sub register_reload_command { - my ($self, $class, $description) = @_; + my ($self, $description) = @_; + + my $class = ref($self); $class->register_method({ name => 'reload', @@ -620,7 +692,9 @@ sub register_reload_command { } sub register_stop_command { - my ($self, $class, $description) = @_; + my ($self, $description) = @_; + + my $class = ref($self); $class->register_method({ name => 'stop', @@ -643,7 +717,9 @@ sub register_stop_command { } sub register_status_command { - my ($self, $class, $description) = @_; + my ($self, $description) = @_; + + my $class = ref($self); $class->register_method({ name => 'status',