Skip to content

Commit

Permalink
Daemon improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Vyacheslav Matyukhin committed Jul 13, 2011
1 parent 8a2ceb6 commit 5f5308c
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 58 deletions.
4 changes: 4 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Revision history for Ubic

{{$NEXT}}
* Ubic::Daemon improvements:
- 'cwd' and 'env' options (SimpleDaemon now just passes them to start_daemon())
- log signal name, exit code and other events to ubic_log prettier and more consistently
* document 'cwd' and 'env' options in Ubic::Service::SimpleDaemon

1.32_01 2011-07-13
* new options in Ubic::Service::SimpleDaemon:
Expand Down
2 changes: 1 addition & 1 deletion dist.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = Ubic
version = 1.32_01
version = 1.32_02
author = Vyacheslav Matyukhin <[email protected]>
license = Perl_5
copyright_holder = Yandex LLC
Expand Down
90 changes: 68 additions & 22 deletions lib/Ubic/Daemon.pm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use POSIX qw(setsid);
use Time::HiRes qw(sleep);
use Params::Validate qw(:all);
use Carp;
use Config;

use Ubic::Lockf;
use Ubic::Daemon::Status;
Expand Down Expand Up @@ -60,6 +61,18 @@ sub import {
__PACKAGE__->export_to_level(1, @_);
}

{
my @signame;
sub _signame {
my $signum = shift;
unless (@signame) {
@signame = split /\s+/, $Config{sig_name};
}
return $signame[$signum];

}
}

sub _log {
my $fh = shift;
return unless defined $fh;
Expand Down Expand Up @@ -177,10 +190,18 @@ Write all daemon's error output to given file. If not specified, all stderr will
=item I<ubic_log>
Optional filename of ubic log. It will contain some technical information about running daemon.
Optional filename of ubic log. Log will contain some technical information about running daemon.
If not specified, this logging facility will be disabled.
=item I<cwd>
Change working directory before starting a daemon. Optional.
=item I<env>
Modify environment before starting a daemon. Optional. Must be a plain hashref if specified.
=item I<term_timeout>
Can contain integer number of seconds to wait between sending I<SIGTERM> and I<SIGKILL> to daemon.
Expand All @@ -203,9 +224,11 @@ sub start_daemon($) {
ubic_log => { type => SCALAR, optional => 1 },
user => { type => SCALAR, optional => 1 },
term_timeout => { type => SCALAR, default => 10, regex => qr/^\d+$/ },
cwd => { type => SCALAR, optional => 1 },
env => { type => HASHREF, optional => 1 },
});
my ($bin, $function, $name, $pidfile, $stdout, $stderr, $ubic_log, $user, $term_timeout)
= @options{qw/ bin function name pidfile stdout stderr ubic_log user term_timeout /};
my ($bin, $function, $name, $pidfile, $stdout, $stderr, $ubic_log, $user, $term_timeout, $cwd, $env)
= @options{qw/ bin function name pidfile stdout stderr ubic_log user term_timeout cwd env /};
if (not defined $bin and not defined $function) {
croak "One of 'bin' and 'function' should be specified";
}
Expand Down Expand Up @@ -240,7 +263,7 @@ sub start_daemon($) {
my $ubic_fh;
my $lock;
my $instant_exit = sub {
my $status = shift; # nobody cares for this status, anyway...
my $status = shift; # nobody cares for this status anyway...
close($ubic_fh) if $ubic_fh;
STDOUT->flush;
STDERR->flush;
Expand Down Expand Up @@ -314,7 +337,7 @@ sub start_daemon($) {
}
_log($ubic_fh, "sending SIGKILL to $child");
kill -9 => $child;
_log($ubic_fh, "child probably killed by SIGKILL");
_log($ubic_fh, "daemon $child probably killed by SIGKILL");
$pid_state->remove();
$instant_exit->(0);
};
Expand All @@ -338,25 +361,33 @@ sub start_daemon($) {
$? = 0;
waitpid($child, 0);
if ($? > 0) {
my $msg;
my $signal = $? & 127;
if ($signal) {
my $msg = "daemon $child failed with \$? = $?";
if (my $signal = $? & 127) {
if ($sigterm_sent && $signal == &POSIX::SIGTERM) {
# it's ok, we probably sent this signal ourselves
_log($ubic_fh, "daemon exited by sigterm");
_log($ubic_fh, "daemon $child exited by sigterm");
$pid_state->remove;
$instant_exit->(0);
}
$msg = "Daemon $child failed with signal $signal";
my $signame = _signame($signal);
if (defined $signame) {
$msg = "daemon $child failed with signal $signame ($signal)";
}
else {
$msg = "daemon $child failed with signal $signal";
}
}
else {
$msg = "Daemon failed: $?";
elsif ($? & 128) {
$msg = "daemon $child failed, core dumped";
}
elsif (my $code = $? >> 8) {
$msg = "daemon $child failed, exit code $code";
}
_log($ubic_fh, $msg);
$pid_state->remove;
$instant_exit->(1);
}
_log($ubic_fh, "daemon exited");
_log($ubic_fh, "daemon $child exited");
$pid_state->remove;
$instant_exit->(0);
}
Expand All @@ -370,6 +401,15 @@ sub start_daemon($) {
setpgrp;
$0 = "ubic-daemon $name";

if (defined $cwd) {
chdir $cwd or die "chdir to '$cwd' failed: $!";
}
if (defined $env) {
for my $key (keys %{ $env }) {
$ENV{$key} = $env->{$key};
}
}

print {$write_pipe} "execing into daemon\n" or die "Can't write to pipe: $!";
close($write_pipe) or die "Can't close pipe: $!";

Expand All @@ -383,7 +423,6 @@ sub start_daemon($) {
else {
$function->();
}

}
};
if ($write_pipe) {
Expand Down Expand Up @@ -415,7 +454,14 @@ Returns instance of L<Ubic::Daemon::Status> class if daemon is alive, and false
=cut
sub check_daemon {
my ($pidfile) = @_;
my $pidfile = shift;
my $options = validate(@_, {
quiet => { optional => 1 },
});

my $print = sub {
print @_, "\n" unless $options->{quiet};
};

my $pid_state = Ubic::Daemon::PidState->new($pidfile);
return undef if $pid_state->is_empty;
Expand All @@ -442,7 +488,7 @@ sub check_daemon {
}
unless ($OS->pid_exists($piddata->{daemon})) {
$pid_state->remove;
print "pidfile $pidfile removed - daemon with cached pid $piddata->{daemon} not found\n";
$print->("pidfile $pidfile removed - daemon with cached pid $piddata->{daemon} not found");
return undef;
}

Expand All @@ -451,19 +497,19 @@ sub check_daemon {

my $guid = $OS->pid2guid($piddata->{daemon});
unless ($guid) {
print "daemon '$daemon_cmd' from $pidfile just disappeared\n";
$print->("daemon '$daemon_cmd' from $pidfile just disappeared");
return undef;
}
if ($guid eq $piddata->{guid}) {
print "killing unguarded daemon '$daemon_cmd' with pid $piddata->{daemon} from $pidfile\n";
$print->("killing unguarded daemon '$daemon_cmd' with pid $piddata->{daemon} from $pidfile");
kill -9 => $piddata->{daemon};
$pid_state->remove;
print "pidfile $pidfile removed\n";
$print->("pidfile $pidfile removed");
return undef;
}
print "daemon pid $piddata->{daemon} cached in pidfile $pidfile, ubic-guardian not found\n";
print "current process '$daemon_cmd' with that pid looks too fresh and will not be killed\n";
print "pidfile $pidfile removed\n";
$print->("daemon pid $piddata->{daemon} cached in pidfile $pidfile, ubic-guardian not found");
$print->("current process '$daemon_cmd' with that pid looks too fresh and will not be killed");
$print->("pidfile $pidfile removed");
$pid_state->remove;
return undef;
}
Expand Down
41 changes: 14 additions & 27 deletions lib/Ubic/Service/SimpleDaemon.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ This module uses L<Ubic::Daemon> module for process daemonization. All pidfiles

use parent qw(Ubic::Service::Skeleton);

use Cwd;
use Ubic::Daemon qw(start_daemon stop_daemon check_daemon);
use Ubic::Result qw(result);
use Ubic::Settings;
use File::Spec;

use Params::Validate qw(:all);

Expand Down Expand Up @@ -83,6 +81,18 @@ File into which daemon's stdout will be redirected. Default is C</dev/null>.
File into which daemon's stderr will be redirected. Default is C</dev/null>.
=item I<ubic_log>
Optional filename of ubic log. Log will contain some technical information about running daemon.
=item I<cwd>
Change working directory before starting a daemon. Optional.
=item I<env>
Modify environment before starting a daemon. Optional. Must be a plain hashref if specified.
=item I<name>
Service's name.
Expand Down Expand Up @@ -123,37 +133,14 @@ sub pidfile {
sub start_impl {
my ($self) = @_;

my $old_cwd;
if (defined $self->{cwd}) {
$old_cwd = getcwd;
chdir $self->{cwd} or die "chdir to '$self->{cwd}' failed: $!";
}

local %ENV = %ENV;
if (defined $self->{env}) {
for my $key (keys %{ $self->{env} }) {
$ENV{$key} = $self->{env}{$key};
}
}

my $start_params = {
pidfile => $self->pidfile,
bin => $self->{bin},
stdout => $self->{stdout} || "/dev/null",
stderr => $self->{stderr} || "/dev/null",
ubic_log => $self->{ubic_log} || "/dev/null",
};
if ($old_cwd) {
for my $key (qw/ pidfile stdout stderr ubic_log /) {
next unless defined $start_params->{$key};
$start_params->{$key} = File::Spec->rel2abs($start_params->{$key}, $old_cwd);
}
for (qw/ env cwd stdout stderr ubic_log /) {
$start_params->{$_} = $self->{$_} if defined $self->{$_};
}
start_daemon($start_params);

if (defined $old_cwd) {
chdir $old_cwd or die "chdir to '$old_cwd' failed: $!";
}
}

sub user {
Expand Down
78 changes: 73 additions & 5 deletions t/daemon.t
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use strict;
use warnings;

use Test::More tests => 23;
use Test::More tests => 28;
use Test::Exception;

use lib 'lib';
Expand Down Expand Up @@ -88,10 +88,7 @@ qr{\QError: Can't write to 'tfiles/non-existent/forbidden.log'\E},
my ($pid) = $piddata =~ /pid\s+(\d+)/ or die "Unknown pidfile content '$piddata'";
kill -9 => $pid;
sleep 1;
{
my $ignore_warn = ignore_warn(qr{(killing unguarded daemon|pidfile tfiles/pid removed)});
ok(!check_daemon("tfiles/pid"), 'ubic-guardian is dead');
}
ok(!check_daemon("tfiles/pid", { quiet => 1 }), 'ubic-guardian is dead');

start_daemon({
bin => "$perl t/bin/locking-daemon",
Expand Down Expand Up @@ -241,3 +238,74 @@ qr{\QError: Can't write to 'tfiles/non-existent/forbidden.log'\E},
lives_ok(sub { stop_daemon('aeuklryaweur') }, 'stop_daemon with non-existing pidfile is ok');
dies_ok(sub { stop_daemon({ pidfile => 'auerawera' }) }, 'calling stop_daemon with invalid parameters is wrong');
}

# ubic_log (5)
{
{
rebuild_tfiles; local_ubic;
start_daemon({
bin => "sleep 10",
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ exited by sigterm$/m, 'exit via sigterm');
}

{
rebuild_tfiles; local_ubic;
start_daemon({
bin => q|perl -e '$SIG{TERM} = sub { exit }; sleep 10'|,
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ exited$/m, 'exit voluntarily');
}

{
rebuild_tfiles; local_ubic;
start_daemon({
bin => q|perl -e 'exit 3'|,
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
});
sleep 1;
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ failed, exit code 3$/m, 'exit with non-zero code');
}

{
rebuild_tfiles; local_ubic;
start_daemon({
bin => q|perl -e '$SIG{TERM} = "IGNORE"; sleep 30'|,
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
term_timeout => 1,
});
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ probably killed by SIGKILL$/m, 'exit via sigkill');
}

{
rebuild_tfiles; local_ubic;
start_daemon({
bin => q|perl -e '$SIG{TERM} = "IGNORE"; sleep 30'|,
pidfile => "tfiles/pid",
ubic_log => 'tfiles/ubic.log',
term_timeout => 1,
});
sleep 1;
my $status = check_daemon('tfiles/pid');
kill 9 => $status->pid;
sleep 1;
stop_daemon('tfiles/pid');
my $log = slurp('tfiles/ubic.log');
like($log, qr/daemon \d+ failed with signal KILL \(9\)$/m, 'exit via signal to daemon');
}
}
1 change: 0 additions & 1 deletion t/setup.t
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use Config;
my $perl = $Config{perlpath};

local $ENV{HOME} = 'tfiles';
warn $ENV{PERL5LIB};

xsystem("$perl ./bin/ubic-admin setup --batch-mode --no-install-services --local");

Expand Down
Loading

0 comments on commit 5f5308c

Please sign in to comment.