Subject: | Sys::Syslog might call exit which triggers DESTROY |
Perl Version: 5.8.8
Distribution name: Sys::Syslog
Operatingsystem: HP-UX, Solaris, Linux, possibliy any POSIX system
There is a serious problem (bug) in the subroutine '_syslog_send_console'
of the perl module Sys::Syslog. The problem arises due to the usage of
exit to terminate a forked child process. Calling exit will trigger
calls to any DESTROY methods of objects that are still alive at the
time of the exit call. This script can be used to reproduce this problems.
Use this command to reproduce the problem:
destructor.pl fork
Use this commands to try possible solutions for the problem
destructor.pl fork exec
destructor.pl fork _exit
In our special case, our appliaction opened a database connection (DBI +
Oracle DBD)
and then called the syslog subroutine from Sys::Syslog. After this call
to syslog
our appliaction tried to use the DIB connection which produced an error
as the
connection has been closed by the child process created in syslog (when
it triggered
the DESTROY method of the oracle connection instance).
For better understanding here is the chronology of the problem:
PARENT CHILD ACTION
X open DBI connection
X call syslog(...) which in turn calls _syslog_send_console
X fork and waitpid
X write log message to /dev/console
X call exit(...)
X exit triggers DESTROY for all alive objects (class instances)
X oracle DBD DESTROY tells the database server that the
connection
must be closed, then it closes the client side of the
connection
(system call close(...)) while the database server closes the
server side of the connection.
X The parent process tries to used the DBI connection which
results
in an error as it has been closed on the server side. The
parent's
filedescriptor is still open and valid but the peer (database)
has closed the connection.
So in order to solve this problem it must be prevented that the child
process
triggers the calls to DESTROY when it terminates.
In my opinion there are three possible solutions to the problem (bugfix):
1. Do not use fork at all in _syslog_send_console
2. Do not call exit(...) in _syslog_send_console use POSIX::_exit(...)
instead.
_exit(...) will not trigger any calls to DESTROY.
3. Do not call exit(...) in _syslog_send_console use exec instead, for
example
exec 'echo', this will prevent any calls to DESTROY.
So my suggestion is to change the following subroutine from Sys::Syslog
sub _syslog_send_console {
my ($buf) = @_;
chop($buf); # delete the NUL from the end
# The console print is a method which could block
# so we do it in a child process and always return success
# to the caller.
if (my $pid = fork) {
if ($options{nowait}) {
return 1;
} else {
if (waitpid($pid, 0) >= 0) {
return ($? >> 8);
} else {
# it's possible that the caller has other
# plans for SIGCHLD, so let's not interfere
return 1;
}
}
} else {
if (open(CONS, ">/dev/console")) {
my $ret = print CONS $buf . "\r"; # XXX: should this be \x0A ?
exit $ret if defined $pid;
close CONS;
}
exit if defined $pid;
}
}
to this:
sub _syslog_send_console {
my ($buf) = @_;
chop($buf); # delete the NUL from the end
# The console print is a method which could block
# so we do it in a child process and always return success
# to the caller.
if (my $pid = fork) {
if ($options{nowait}) {
return 1;
} else {
if (waitpid($pid, 0) >= 0) {
return ($? >> 8);
} else {
# it's possible that the caller has other
# plans for SIGCHLD, so let's not interfere
return 1;
}
}
} else {
use POSIX;
if (open(CONS, ">/dev/console")) {
my $ret = print CONS $buf . "\r"; # XXX: should this be \x0A ?
POSIX::_exit($ret) if defined $pid;
close CONS;
}
POSIX::_exit(0) if defined $pid;
}
}
Subject: | destructor.pl |
#!/usr/bin/perl -w
package XX;
sub DESTROY {
my $self = shift;
printf "%s.DESTROY(pid=%s)\n", $self, $$;
}
package main;
my $x = bless {}, 'XX';
if ( $ARGV[0] && $ARGV[0] eq 'fork' ) {
my $pid = fork;
if ( $pid ) {
waitpid($pid, 0);
} else {
if ( $ARGV[1] && $ARGV[1] eq 'exec' ) {
exec 'echo';
} elsif ( $ARGV[1] && $ARGV[1] eq '_exit' ) {
use POSIX;
POSIX::_exit(0);
}
exit(0);
}
}
exit(0);
__DATA__
There is a serious problem (bug) in the subroutine '_syslog_send_console'
of the perl module Sys::Syslog. The problem arises due to the usage of
exit to terminate a forked child process. Calling exit will trigger
calls to any DESTROY methods of objects that are still alive at the
time of the exit call. This script can be used to reproduce this problems.
Use this command to reproduce the problem:
destructor.pl fork
Use this commands to try possible solutions for the problem
destructor.pl fork exec
destructor.pl fork _exit
In our special case, our appliaction opened a database connection (DBI + Oracle DBD)
and then called the syslog subroutine from Sys::Syslog. After this call to syslog
our appliaction tried to use the DIB connection which produced an error as the
connection has been closed by the child process created in syslog (when it triggered
the DESTROY method of the oracle connection instance).
For better understanding here is the chronology of the problem:
PARENT CHILD ACTION
X open DBI connection
X call syslog(...) which in turn calls _syslog_send_console
X fork and waitpid
X write log message to /dev/console
X call exit(...)
X exit triggers DESTROY for all alive objects (class instances)
X oracle DBD DESTROY tells the database server that the connection
must be closed, then it closes the client side of the connection
(system call close(...)) while the database server closes the
server side of the connection.
X The parent process tries to used the DBI connection which results
in an error as it has been closed on the server side. The parent's
filedescriptor is still open and valid but the peer (database)
has closed the connection.
So in order to solve this problem it must be prevented that the child process
triggers the calls to DESTROY when it terminates.
In my opinion there are three possible solutions to the problem (bugfix):
1. Do not use fork at all in _syslog_send_console
2. Do not call exit(...) in _syslog_send_console use POSIX::_exit(...) instead.
_exit(...) will not trigger any calls to DESTROY.
3. Do not call exit(...) in _syslog_send_console use exec instead, for example
exec 'echo', this will prevent any calls to DESTROY.
So my suggestion is to change the following subroutine from Sys::Syslog
sub _syslog_send_console {
my ($buf) = @_;
chop($buf); # delete the NUL from the end
# The console print is a method which could block
# so we do it in a child process and always return success
# to the caller.
if (my $pid = fork) {
if ($options{nowait}) {
return 1;
} else {
if (waitpid($pid, 0) >= 0) {
return ($? >> 8);
} else {
# it's possible that the caller has other
# plans for SIGCHLD, so let's not interfere
return 1;
}
}
} else {
if (open(CONS, ">/dev/console")) {
my $ret = print CONS $buf . "\r"; # XXX: should this be \x0A ?
exit $ret if defined $pid;
close CONS;
}
exit if defined $pid;
}
}
to this:
sub _syslog_send_console {
my ($buf) = @_;
chop($buf); # delete the NUL from the end
# The console print is a method which could block
# so we do it in a child process and always return success
# to the caller.
if (my $pid = fork) {
if ($options{nowait}) {
return 1;
} else {
if (waitpid($pid, 0) >= 0) {
return ($? >> 8);
} else {
# it's possible that the caller has other
# plans for SIGCHLD, so let's not interfere
return 1;
}
}
} else {
use POSIX;
if (open(CONS, ">/dev/console")) {
my $ret = print CONS $buf . "\r"; # XXX: should this be \x0A ?
POSIX::_exit($ret) if defined $pid;
close CONS;
}
POSIX::_exit(0) if defined $pid;
}
}