Commit 11e55444 authored by sikeda's avatar sikeda
Browse files

[dev] Refactoring. Sympa::Tools::Daemon has been merged to Sympa::Process.


git-svn-id: https://subversion.renater.fr/sympa/branches/sympa-6.2-branch@12512 05aa8bb8-cd2b-0410-b1d7-8918dfa770ce
parent c0b1444a
......@@ -95,7 +95,6 @@ nobase_modules_DATA = \
Sympa/Task.pm \
Sympa/Template.pm \
tools.pm \
Sympa/Tools/Daemon.pm \
Sympa/Tools/Data.pm \
Sympa/Tools/DKIM.pm \
Sympa/Tools/File.pm \
......
......@@ -387,7 +387,7 @@ If set, maximum number of invocation of sendmail is divided by this value.
=head1 SEE ALSO
L<Sympa::Alarm>, L<Sympa::Bulk>, L<Sympa::Message>.
L<Sympa::Alarm>, L<Sympa::Bulk>, L<Sympa::Message>, L<Sympa::Process>.
=head1 HISTORY
......
......@@ -26,14 +26,18 @@ package Sympa::Process;
use strict;
use warnings;
use base qw(Class::Singleton);
use English qw(-no_match_vars);
use POSIX qw();
use Conf;
use Sympa;
use Sympa::Constants;
use Sympa::Language;
use Sympa::LockedFile;
use Sympa::Log;
use Sympa::Tools::File;
use base qw(Class::Singleton);
my $log = Sympa::Log->instance;
......@@ -44,6 +48,16 @@ sub _new_instance {
bless {children => {}} => $class;
}
sub init {
my $self = shift;
my %options = @_;
foreach my $key (sort keys %options) {
$self->{$key} = $options{$key};
}
$self;
}
sub fork {
my $self = shift;
my $tag = shift || [split /\//, $PROGRAM_NAME]->[-1];
......@@ -107,9 +121,8 @@ sub reap_child {
}
}
if ($options{pidname}) {
my $pidname = $options{pidname};
foreach my $child_pid (_get_pids_in_pid_file($pidname)) {
if ($self->{pidname} and $options{file}) {
foreach my $child_pid (_get_pids_in_pid_file($self->{pidname})) {
next if $child_pid == $PID;
unless (exists $self->{children}->{$child_pid}) {
......@@ -118,12 +131,8 @@ sub reap_child {
'The %s child exists in the PID file but is no longer running. Removing it and notifying listmaster',
$child_pid
);
Sympa::Tools::Daemon::remove_pid($pidname, $child_pid,
{multiple_process => 1});
Sympa::Tools::Daemon::send_crash_report(
pid => $child_pid,
pname => [split /\//, $PROGRAM_NAME]->[-1]
);
$self->remove_pid(pid => $child_pid);
_send_crash_report($child_pid);
}
}
}
......@@ -131,6 +140,225 @@ sub reap_child {
return $pid;
}
# Moved to Log::_daemon_name().
#sub get_daemon_name;
# Old name: Sympa::Tools::Daemon::remove_pid().
sub remove_pid {
my $self = shift;
my %options = @_;
my $name = $self->{pidname}
or die 'bug in logic. Ask developer';
my $pid = $options{pid} || $PID;
my $piddir = Sympa::Constants::PIDDIR;
my $pidfile = $piddir . '/' . $name . '.pid';
my @pids;
# Lock pid file
my $lock_fh = Sympa::LockedFile->new($pidfile, 5, '+<');
unless ($lock_fh) {
$log->syslog('err', 'Could not open %s to remove PID %s',
$pidfile, $pid);
return undef;
}
## If in multi_process mode (bulk.pl for instance can have child
## processes) then the PID file contains a list of space-separated PIDs
## on a single line
unless ($options{final}) {
# Read pid file
seek $lock_fh, 0, 0;
my $l = <$lock_fh>;
@pids = grep { /^[0-9]+$/ and $_ != $pid } split(/\s+/, $l);
## If no PID left, then remove the file
unless (@pids) {
## Release the lock
unless (unlink $pidfile) {
$log->syslog('err', "Failed to remove %s: %m", $pidfile);
$lock_fh->close;
return undef;
}
} else {
seek $lock_fh, 0, 0;
truncate $lock_fh, 0;
print $lock_fh join(' ', @pids) . "\n";
}
} else {
unless (unlink $pidfile) {
$log->syslog('err', "Failed to remove %s: %m", $pidfile);
$lock_fh->close;
return undef;
}
my $err_file = $Conf::Conf{'tmpdir'} . '/' . $pid . '.stderr';
if (-f $err_file) {
unless (unlink $err_file) {
$log->syslog('err', "Failed to remove %s: %m", $err_file);
$lock_fh->close;
return undef;
}
}
}
$lock_fh->close;
return 1;
}
# Old name: Sympa::Tools::Daemon::write_pid().
sub write_pid {
my $self = shift;
my %options = @_;
my $name = $self->{pidname}
or die 'bug in logic. Ask developer';
my $pid = $options{pid} || $PID;
my $piddir = Sympa::Constants::PIDDIR;
my $pidfile = $piddir . '/' . $name . '.pid';
## Create piddir
mkdir($piddir, 0755) unless (-d $piddir);
unless (
Sympa::Tools::File::set_file_rights(
file => $piddir,
user => Sympa::Constants::USER,
group => Sympa::Constants::GROUP,
)
) {
die sprintf 'Unable to set rights on %s. Exiting.', $piddir;
## No return
}
my @pids;
# Lock pid file
my $lock_fh = Sympa::LockedFile->new($pidfile, 5, '+>>');
unless ($lock_fh) {
die sprintf 'Unable to lock %s file in write mode. Exiting.',
$pidfile;
}
## If pidfile exists, read the PIDs
if (-s $pidfile) {
# Read pid file
seek $lock_fh, 0, 0;
my $l = <$lock_fh>;
@pids = grep {/^[0-9]+$/} split(/\s+/, $l);
}
# If we can have multiple instances for the process.
# Print other pids + this one.
unless ($options{initial}) {
## Print other pids + this one
push(@pids, $pid);
seek $lock_fh, 0, 0;
truncate $lock_fh, 0;
print $lock_fh join(' ', @pids) . "\n";
} else {
## The previous process died suddenly, without pidfile cleanup
## Send a notice to listmaster with STDERR of the previous process
if (@pids) {
my $other_pid = $pids[0];
$log->syslog('notice',
'Previous process %s died suddenly; notifying listmaster',
$other_pid);
_send_crash_report($other_pid);
}
seek $lock_fh, 0, 0;
unless (truncate $lock_fh, 0) {
## Unlock pid file
$lock_fh->close();
die sprintf 'Could not truncate %s, exiting.', $pidfile;
}
print $lock_fh $pid . "\n";
}
unless (
Sympa::Tools::File::set_file_rights(
file => $pidfile,
user => Sympa::Constants::USER,
group => Sympa::Constants::GROUP,
)
) {
## Unlock pid file
$lock_fh->close();
die sprintf 'Unable to set rights on %s', $pidfile;
}
## Unlock pid file
$lock_fh->close();
return 1;
}
# Old name: Sympa::Tools::Daemon::direct_stderr_to_file().
sub direct_stderr_to_file {
my $self = shift;
# Error output is stored in a file with PID-based name.
# Useful if process crashes.
open(STDERR, '>>', $Conf::Conf{'tmpdir'} . '/' . $PID . '.stderr');
unless (
Sympa::Tools::File::set_file_rights(
file => $Conf::Conf{'tmpdir'} . '/' . $PID . '.stderr',
user => Sympa::Constants::USER,
group => Sympa::Constants::GROUP,
)
) {
$log->syslog(
'err',
'Unable to set rights on %s: %m',
$Conf::Conf{'tmpdir'} . '/' . $PID . '.stderr'
);
return undef;
}
return 1;
}
# Old name: Sympa::Tools::Daemon::send_crash_report().
sub _send_crash_report {
my $pid = shift;
my $err_file = $Conf::Conf{'tmpdir'} . '/' . $pid . '.stderr';
my $language = Sympa::Language->instance;
my (@err_output, $err_date);
if (-f $err_file) {
open(ERR, $err_file);
@err_output = map { chomp $_; $_; } <ERR>;
close ERR;
my $err_date_epoch = (stat $err_file)[9];
if (defined $err_date_epoch) {
$err_date = $language->gettext_strftime("%d %b %Y %H:%M",
localtime $err_date_epoch);
} else {
$err_date = $language->gettext('(unknown date)');
}
} else {
$err_date = $language->gettext('(unknown date)');
}
Sympa::send_notify_to_listmaster(
'*', 'crash',
{ 'crashed_process' => [split /\//, $PROGRAM_NAME]->[-1],
'crash_err' => \@err_output,
'crash_date' => $err_date,
'pid' => $pid,
}
);
}
# return a lockname that is a uniq id of a processus (hostname + pid) ;
# hostname(20) and pid(10) are truncated in order to store lockname in
# database varchar(30)
# DEPRECATED: No longer used.
#sub get_lockname();
# Old name: Sympa::Tools::Daemon::get_pids_in_pid_file().
sub _get_pids_in_pid_file {
my $name = shift;
......@@ -150,6 +378,19 @@ sub _get_pids_in_pid_file {
return @pids;
}
# Old name: Sympa::Tools::Daemon::get_children_processes_list().
# OBSOLETED. No longer used.
sub get_children_processes_list {
$log->syslog('debug3', '');
my @children;
for my $p (@{Proc::ProcessTable->new->table}) {
if ($p->ppid == $PID) {
push @children, $p->pid;
}
}
return @children;
}
1;
__END__
......@@ -185,6 +426,11 @@ Returns:
A new L<Sympa::Process> instance, or I<undef> for failure.
=item init ( key =E<gt> value, ... )
I<Instance method>.
TBD.
=item fork ( [ $tag ] )
I<Instance method>.
......@@ -207,7 +453,7 @@ Returns:
See L<perlfunc/"fork">.
=item reap_child ( [ blocking =E<gt> 1 ], [ children =E<gt> \%children ],
pidname =E<gt> $name ] )
file =E<gt> 1 ] )
I<Instance method>.
Non blocking function called by: main loop of sympa, task_manager, bounced
......@@ -226,17 +472,50 @@ Operation would block.
Syncs PIDs in local map %children.
=item pidname =E<gt> $name
=item file =E<gt> 1
Syncs child PIDs in PID file named $name.pid.
If dead PID is found, notification will be send to listmaster.
Syncs child PIDs in PID file.
If dead PID is found, notification will be sent to super-listmaster.
=back
Returns:
PID of reaped child porocess.
C<-1> on error.
PID of reaped child porocess or C<0>.
If blocking option is set, returns C<0>.
In both cases returns C<-1> on failure.
=item remove_pid ([ pid =E<gt> $pid ], [ final =E<gt> 1 ] )
I<Instance method>.
Removes process ID from PID file.
Then if the file is empty, it will be removed.
=item write_pid ( [ initial =E<gt> 1 ], [ pid =E<gt> $pid ] )
I<Instance method>.
Writes or adds process ID to PID file.
Parameters:
=over
=item initial =E<gt> 1
Initializes PID file.
If the file remains, notification will be sent to super-listmaster.
=item pid =E<gt> $pid
Process ID to be written.
By default PID of current process.
=back
=item direct_stderr_to_file ( )
I<Instance method>.
TBD.
=back
......@@ -248,12 +527,15 @@ L<Sympa::Process> instance may have following attributes:
=item {children}
TBD.
Hashref with child PIDs forked by fork() method as keys.
=back
=head1 HISTORY
L<Sympa::Process> appeared on Sympa 6.2.12.
L<Sympa::Tools::Daemon> appeared on Sympa 6.2a.41.
Renamed L<Sympa::Process> appeared on Sympa 6.2.12
and began to provide OO interface.
=cut
......@@ -36,6 +36,7 @@ use Sympa::Family;
use Sympa::List;
use Sympa::Log;
use Sympa::Mailer;
use Sympa::Process;
use Sympa::Report;
use Sympa::Tools::Data;
......@@ -56,7 +57,7 @@ sub _init {
Sympa::Alarm->instance->flush;
} elsif ($state == 2) {
# Free zombie sendmail process.
Sympa::Mailer->instance->reaper;
Sympa::Process->instance->reap_child;
}
1;
......
......@@ -38,7 +38,7 @@ use Sympa::Alarm;
use Conf;
use Sympa::List;
use Sympa::Log;
use Sympa::Mailer;
use Sympa::Process;
use Sympa::Regexps;
use Sympa::Scenario;
use Sympa::Tools::Data;
......@@ -61,7 +61,7 @@ sub _init {
Sympa::Alarm->instance->flush;
} elsif ($state == 2) {
# Free zombie sendmail process.
Sympa::Mailer->instance->reaper;
Sympa::Process->instance->reap_child;
}
1;
......
......@@ -36,7 +36,7 @@ use Sympa::Archive;
use Conf;
use Sympa::List;
use Sympa::Log;
use Sympa::Mailer;
use Sympa::Process;
use Sympa::Tools::File;
use base qw(Sympa::Spindle);
......@@ -55,7 +55,7 @@ sub _init {
Sympa::Alarm->instance->flush;
} elsif ($state == 2) {
# Free zombie sendmail process.
Sympa::Mailer->instance->reaper;
Sympa::Process->instance->reap_child;
}
1;
......
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015 GIP RENATER
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
=encoding utf-8
=head1 NAME
Sympa::Tools::Daemon - Daemon-related functions
=head1 DESCRIPTION
This package provides some daemon-related functions.
=head2 Functions
=cut
package Sympa::Tools::Daemon;
use strict;
use warnings;
use English qw(no_match_vars);
use Proc::ProcessTable;
use Sys::Hostname qw();
use Sympa;
use Sympa::Constants;
use Sympa::Language;
use Sympa::LockedFile;
use Sympa::Log;
use Sympa::Tools::File;
my $log = Sympa::Log->instance;
# Moved to Log::_daemon_name().
#sub get_daemon_name;
=over
=item remove_pid($name, $pid, $options)
Remove PID file and STDERR output.
=back
=cut
sub remove_pid {
my ($name, $pid, $options) = @_;
my $piddir = Sympa::Constants::PIDDIR;
my $pidfile = $piddir . '/' . $name . '.pid';
my @pids;
# Lock pid file
my $lock_fh = Sympa::LockedFile->new($pidfile, 5, '+<');
unless ($lock_fh) {
$log->syslog('err', 'Could not open %s to remove PID %s',
$pidfile, $pid);
return undef;
}
## If in multi_process mode (bulk.pl for instance can have child
## processes) then the PID file contains a list of space-separated PIDs
## on a single line
if ($options->{'multiple_process'}) {
# Read pid file
seek $lock_fh, 0, 0;
my $l = <$lock_fh>;
@pids = grep { /^[0-9]+$/ and $_ != $pid } split(/\s+/, $l);
## If no PID left, then remove the file
unless (@pids) {
## Release the lock
unless (unlink $pidfile) {
$log->syslog('err', "Failed to remove %s: %m", $pidfile);
$lock_fh->close;
return undef;
}
} else {
seek $lock_fh, 0, 0;
truncate $lock_fh, 0;
print $lock_fh join(' ', @pids) . "\n";
}
} else {
unless (unlink $pidfile) {
$log->syslog('err', "Failed to remove %s: %m", $pidfile);
$lock_fh->close;
return undef;
}
my $err_file = $Conf::Conf{'tmpdir'} . '/' . $pid . '.stderr';
if (-f $err_file) {
unless (unlink $err_file) {
$log->syslog('err', "Failed to remove %s: %m", $err_file);
$lock_fh->close;
return undef;
}
}
}
$lock_fh->close;
return 1;
}
=over
=item write_pid($name, $pid, $options)
TBD.
=back
=cut
sub write_pid {
my ($name, $pid, $options) = @_;
my $piddir = Sympa::Constants::PIDDIR;
my $pidfile = $piddir . '/' . $name . '.pid';
## Create piddir
mkdir($piddir, 0755) unless (-d $piddir);
unless (
Sympa::Tools::File::set_file_rights(
file => $piddir,
user => Sympa::Constants::USER,
group => Sympa::Constants::GROUP,
)