Skip Menu |

This queue is for tickets about the Log-Log4perl CPAN distribution.

Report information
The Basics
Id: 84723
Status: resolved
Priority: 0/
Queue: Log-Log4perl

People
Owner: Nobody in particular
Requestors: chris [...] marchex.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Log::Log4Perl::Appender::File destructor changes exit status of child
Date: Thu, 18 Apr 2013 14:38:24 -0700
To: bug-Log-Log4perl [...] rt.cpan.org
From: Christopher Fitzner <chris [...] marchex.com>
Hey there, I just ran in what seems to be a bug in log4perl. I have some code where I'm spawning a child process whose parent uses log4perl. The child inherits the the parents log4perl filehandles and tries to close them upon exit. This close fails but sets $? which changes the return status of the child. Specifically this is happening to me when using cronolog to handle the log rotation. I'm able to work around the issue by setting the filehandle to undef before exiting the child. I've confirmed that the behaviour still occurs in 1.40. Here is some code to replicate this issue (sorry for the length): #!/usr/bin/perl use strict; use warnings; use POSIX ":sys_wait_h"; use Errno; use Log::Log4perl qw(:easy); Log::Log4perl->Log::Log4perl::init('/tmp/logger.conf'); my $desired_child_count = 1; our $logger = get_logger(); # list of active children my %children = (); while (1) { # Figure out how many children are available to do work my $children_available = detect_child_state(); if ($children_available) { spawn_child(); } print "$$: Main process sleeping for 10\n"; sleep 10; } exit 0; sub detect_child_state { local $!; # don't let waitpid() overwrite current error while ((my $pid = waitpid(-1, POSIX::WNOHANG)) > 0) { if (POSIX::WIFEXITED($?)) { my $child_exit_status = POSIX::WEXITSTATUS($?); if ($child_exit_status != 5) { print "$$: child exited with abnormal status: $child_exit_status\n"; } } if (exists $children{$pid} ) { delete $children{$pid}; } } return $desired_child_count - scalar keys %children; } sub spawn_child { if (my $child_pid = fork()) { $children{$child_pid} = 1; } elsif (defined $child_pid) { print "$$: new child sleeping for 10\n"; sleep 10; # here is the work around # undef Log::Log4perl->appenders()->{CRONOLOG}->{appender}->{fh}; exit 5; } } # here is the logger conf I'm using: log4perl.rootLogger=DEBUG, CRONOLOG ##### STANDARD FILE LOGGING ##### log4perl.appender.LOGFILE=Log::Log4perl::Appender::File log4perl.appender.LOGFILE.filename=/usr/bin/cronolog /tmp/test_logfile.log log4perl.appender.LOGFILE.mode=append log4perl.appender.LOGFILE.syswrite=true log4perl.appender.LOGFILE.layout=PatternLayout log4perl.appender.LOGFILE.layout.ConversionPattern=%d %H %c: %p [pid: %P] %l - %m%n ##### FOR USE WITH CRONOLOG ##### log4perl.appender.CRONOLOG=Log::Log4perl::Appender::File log4perl.appender.CRONOLOG.filename=/usr/bin/cronolog /tmp/test_logfile.log log4perl.appender.CRONOLOG.mode=pipe log4perl.appender.CRONOLOG.layout=PatternLayout log4perl.appender.CRONOLOG.layout.ConversionPattern=%d %H %c: %p [pid: %P] %l - %m%n ##### STDERR LOGGING ##### log4perl.appender.STDERR=Log::Log4perl::Appender::Screen log4perl.appender.STDERR.layout=PatternLayout log4perl.appender.STDERR.layout.ConversionPattern=%d %H %c: %p [pid: %P] %l - %m%n ##### STDERR LOGGING WITH COLOR ##### log4perl.appender.ColorScreen=Log::Log4perl::Appender::ScreenColoredLevels log4perl.appender.ColorScreen.layout=PatternLayout log4perl.appender.ColorScreen.layout.ConversionPattern=%d %H %c: %p [pid: %P] %l - %m%n
Looks like there's a problem with the file appender being cleaned up during global destruction if it has a handle to a pipe open. The close() on the file handle attached to the pipe fails with -1 in this case and the exit status of the child changes as well. I'll need to dig deeper to find out what's going on, for now you can work around the problem by running Log::Log4perl->eradicate_appender( "CRONOLOG" ); in the child before you exit, to make sure the appender gets cleaned up before global destruction kicks in.
Thanks for submitting this problem, it is a truly interesting case of how the magic that goes on inside the bowels of perl can confuse even experienced users :). What happens in your use case is that the file appender runs Perl's open() in pipe mode, which spawns a process to run your cronolog program. The corresponding close() of the pipe happens when the appender gets destroyed, namely in its DESTROY method. Problem is that in the meantime, you've spawned a child process and the destruction in the corresponding close() call happens in the child process. For reasons rooting deeply within Perl's implementation of close(), close() will return an error named "No Child Processes" in this case. Here are the steps to reproduce the problem: open PIPE, "| cat" or die $!; my $pid = fork(); if( $pid == 0 ) { # child close PIPE or die $!; exit 0; } sleep 1; my $reaped = waitpid(-1, 0 ); print "pid $reaped\n"; Now, why does this change the exit status of the child? Turns out that what happens in a DESTROY method when the object gets cleaned up (either when it goes out of scope or during global destruction), sets $? which trumps what you set via exit(). So close() fails in the child, sets $? to 255 and that's what you get via POSIX::WEXITSTATUS($?) later in the parent. So what should we do about this? I've added some code to check for this condition and suppress the error: https://github.com/mschilli/log4perl/commit/9b2678cbdb8b8f8ba3902020ed8cd25224024e3a It's not an ideal solution, it'd be ultimately up to Perl to deal with it at a lower level. But hey, can't have everything, hope it helps getting your application back on track, let me know if you have other ideas! -- Mike