jdhedden has asked for the wisdom of the Perl Monks concerning the following question:
I believe there is a bug in Perl (5.8.2) in that this code occasionally exits due to die() - reporting 'CHLD EVENT' even though the eval block should prevent this from happening. (A test program that recreates this bug is provided below.)my $chld_flag = 0; $SIG{'CHLD'} = sub { $chld_flag = 1; }; $SIG{'ALRM'} = 'IGNORE'; while (1) { # Check child processes # Includes using waidpid() to clean up after child processes # Display status # Get user commands my $cmd = ''; eval { # Allow interrupts by alarms and 'child' events local $SIG{'CHLD'} = sub { die("CHLD EVENT\n"); }; local $SIG{'ALRM'} = sub { die("ALRM EVENT\n"); }; alarm(10); if (! $chld_flag) { $cmd = <STDIN>; } }; alarm(0); $chld_flag = 0; # Process user command, if any # Such as launch child processes }
I believe the problem occurs when a SIGALRM and SIGCHLD occur very close together. The SIGALRM causes the program to exit the eval block's scope with the die("ALRM EVENT\n"). This is okay.
However, before the %SIG array is restored with the original SIGCHLD handler (which does not contain a die()), a SIGCHLD occurs and the eval block's local scope SIGCHLD handler (which does contain a die()) is invoked. This is the bug.
I have another very different case that leads me to the same conclusion: A parent process monitors child processes and sends SIGUSR1s to them as needed. The child processes have a global SIGUSR1 handler that sets a flag, and inside an eval block a local SIGUSR1 handler that has a die().
Occassionally, the parent finds child processes that return values from waidpid() with ($? % 255) == 255 which indicates that the child exited due to a die(). This again shows that the Perl interpreter is allowing signals to process just beyond an eval block's scope, but before the %SIG hash has been restored from changes inside the eval block.$SIG{'USR1'} = sub { $flag = 1; }; eval { local $SIG{'USR1'} = sub { die "Timeout\n"; }; DoWork(); };
Would someone familiar with the Perl interpreter's code please verify this problem: Namely, that signal handling is (improperly) allowed to occur following an eval block but before the %SIG hash has been restored to remove any signal handlers local to the eval block.
UPDATE:
As a workaround, I'm embedding each eval block inside another eval block to 'catch' the errant dies().
Is there a better workaround?eval { eval { # Allow interrupts by alarms and 'child' events local $SIG{'CHLD'} = sub { die("CHLD EVENT\n"); }; local $SIG{'ALRM'} = sub { die("ALRM EVENT\n"); }; alarm(10); if (! $chld_flag) { $cmd = <STDIN>; } }; };
2nd UPDATE:
I now have test code to prove this bug exists!!!
Based on this, I have submitted this to perlbug@perl.org.#!/usr/bin/perl ##### # # Test program to reproduce the following Perl bug: # It is possible for a signal handler defined locally inside an # eval blocks to be executed outside the scope of the eval block. # # Just execute this Perl script. # It will eventually (after a few minutes) exit when the bug occurs. # ##### use strict; use warnings; use Time::HiRes qw( usleep ); my $CHILD_MAX = 25; # Max number of children to run my $child_count = 0; # Count of children currently running my $child_done = 0; # Flag that a child has terminated my %child_pids; # Holds child processes' PIDs # Set the flag that a child has terminated $SIG{'CHLD'} = sub { $child_done = 1; }; # Loop until the bug occurs do { # Cleanup any terminated children if ($child_done) { $child_done = 0; # Check all child processes using non-blocking waitpid() call foreach my $pid (keys(%child_pids)) { if (waitpid($pid, 1) == $pid) { # 1 = POSIX::WNOHANG delete($child_pids{$pid}); $child_count--; } } } # Start more children while ($child_count < $CHILD_MAX) { my $pid; if (($pid = fork()) == 0) { # Child sleeps for a random amount of time and then exits my $usec = 950000 + int(rand(100000)); usleep($usec); exit(0); } # Parent remembers the child's PID for later cleanup $child_pids{$pid} = undef; $child_count++; } # Try to recreate the bug eval { eval { # Local signal handler to 'kill' the sleep() call below local $SIG{'CHLD'} = sub { die("SIGCHLD\n"); }; sleep(1); # Hang around a bit }; # Set the flag for cleaning up terminated child processes if ($@ && ($@ =~ /CHLD/)) { $child_done = 1; } }; # Keep looping until the bug occurs } while (! $@); # When we get here, it shows that the signal handler # defined inside the inner eval block above was # executed OUTSIDE the scope of the inner eval block! print("Bug detected: $@"); exit(1); # EOF
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: BUG? die() executing outside scope of eval block
by Zaxo (Archbishop) on Dec 19, 2003 at 04:57 UTC | |
|
Re: BUG? die() executing outside scope of eval block
by liz (Monsignor) on Dec 19, 2003 at 10:08 UTC | |
by jdhedden (Deacon) on Dec 19, 2003 at 13:25 UTC |