Permalink
Browse files

Make SIGDIE asynchronous. It will no longer interrupt long chains of

events (such as _parent/_child chains during session creation and
destruction).

Exceptions in _stop is currently untrappable, as the asynchronous
SIGDIE will have nowhere to be delivered.  They will cause POE::Kernel
to "gracefully" die at the end of _stop's dispatch.

SIGDIE propagation follows the normal rules with one new addition: The
signal also proragates from the target session upwards through parent,
grandparent, and so on.  It will not be dispatched to children of
ancestor sessions.  It will not be dispatched to POE::Kernel itself.

Nearly all regression tests pass.  The notable exception is Jonathan
Steinert's recursive signal tests.  They have become moot since SIGDIE
is not recursive anymore.
  • Loading branch information...
1 parent da7c15e commit da8b9b98860346af8d6c7fd752b58fd0dad49efb @rcaputo committed Feb 29, 2008
View
@@ -166,9 +166,9 @@ t/90_regression/rt1648-tied-stderr.t
t/90_regression/rt19908-merlyn-stop.t
t/90_regression/rt23181-sigchld-rc.t
t/90_regression/steinert-passed-wheel.t
-t/90_regression/steinert-recursive-signal.t
t/90_regression/steinert-signal-integrity.t
t/90_regression/suzman_windows.t
t/90_regression/ton-stop-corruption.t
+t/90_regression/whelan-dieprop.t
t/90_regression/whjackson-followtail.t
TODO
View
@@ -225,8 +225,7 @@ sub ET_MASK_USER () { ~(ET_GC | ET_SCPOLL | ET_STAT) }
# Temporary signal subtypes, used during signal dispatch semantics
# deprecation and reformation.
-sub ET_SIGNAL_RECURSIVE () { 0x1000 } # Explicitly requested signal.
-sub ET_SIGNAL_ANY () { ET_SIGNAL | ET_SIGNAL_RECURSIVE }
+sub ET_SIGNAL_RECURSIVE () { 0x2000 } # Explicitly requested signal.
# A hash of reserved names. It's used to test whether someone is
# trying to use an internal event directly.
@@ -899,6 +898,8 @@ sub _dispatch_event {
$self->_data_sig_reset_handled($signal);
+ # Step 1b: Collect a list of sessions to receive the signal.
+
my @touched_sessions = ($session);
my $touched_index = 0;
while ($touched_index < @touched_sessions) {
@@ -907,18 +908,30 @@ sub _dispatch_event {
$touched_index++;
}
- # Step 2: Propagate the signal to sessions that are watching it.
+ # Step 1c: The DIE signal propagates up through parents, too.
+
+ if ($signal eq "DIE") {
+ my $next_target = $self->_data_ses_get_parent($session);
+ while ($next_target != $self) {
+ unshift @touched_sessions, $next_target;
+ $next_target = $self->_data_ses_get_parent($next_target);
+ }
+ }
+
+ # Step 2: Propagate the signal to the explict watchers in the
+ # child tree. Ensure the full tree is touched regardless
+ # whether there are explicit watchers.
if ($self->_data_sig_explicitly_watched($signal)) {
- $touched_index = @touched_sessions;
my %signal_watchers = $self->_data_sig_watchers($signal);
+
+ $touched_index = @touched_sessions;
while ($touched_index--) {
my $target_session = $touched_sessions[$touched_index];
-
$self->_data_sig_touched_session($target_session);
+ next unless exists $signal_watchers{$target_session};
my $target_event = $signal_watchers{$target_session};
- next unless defined $target_event;
if (TRACE_SIGNALS) {
_warn(
@@ -927,6 +940,8 @@ sub _dispatch_event {
);
}
+ # ET_SIGNAL_RECURSIVE is used here to avoid repropagating
+ # the signal ad nauseam.
$self->_dispatch_event(
$target_session, $self,
$target_event, ET_SIGNAL_RECURSIVE, [ @$etc ],
@@ -935,25 +950,25 @@ sub _dispatch_event {
}
}
else {
- # TODO This is ugly repeated code. See the block just above
- # the else.
-
$touched_index = @touched_sessions;
while ($touched_index--) {
- my $target_session = $touched_sessions[$touched_index];
-
- $self->_data_sig_touched_session(
- $target_session, $event, 0, $etc->[0],
- );
+ $self->_data_sig_touched_session($touched_sessions[$touched_index]);
}
}
# Step 3: Check to see if the signal was handled.
$self->_data_sig_free_terminated_sessions();
+ # If the signal was SIGDIE, then propagate the exception.
+
+ my $handled_session_count = (_data_sig_handled_status())[0];
+ if ($signal eq "DIE" and !$handled_session_count) {
+ $kr_exception = $etc->[1]{error_str};
+ }
+
# Signal completely dispatched. Thanks for flying!
- return (_data_sig_handled_status())[0];
+ return;
}
}
@@ -1031,38 +1046,26 @@ sub _dispatch_event {
);
}
- # While it looks like we're checking the signal handler's return
- # value, we actually aren't. _dispatch_event() for signals
- # returns whether the signal was handled. See the return at the
- # end of "Step 3" in the signal handling procedure.
- my $handled = $self->_dispatch_event(
- $session,
- $source_session,
- EN_SIGNAL,
- ET_SIGNAL,
- [
- 'DIE' => {
- source_session => $source_session,
- dest_session => $session,
- event => $event,
- file => $file,
- line => $line,
- from_state => $fromstate,
- error_str => $exception,
- },
- ],
- __FILE__,
- __LINE__,
- undef,
- time(),
- -__LINE__,
- );
-
- # An exception has occurred. Set a global that we can check at
- # the uppermost level.
- unless ($handled) {
+ # Exceptions in _stop are rethrown unconditionally.
+ # We can't enqueue them--the session is about to go away.
+ if ($type & ET_STOP) {
$kr_exception = $exception;
}
+ else {
+ $self->_data_ev_enqueue(
+ $session, $self, EN_SIGNAL, ET_SIGNAL, [
+ 'DIE' => {
+ source_session => $source_session,
+ dest_session => $session,
+ event => $event,
+ file => $file,
+ line => $line,
+ from_state => $fromstate,
+ error_str => $exception,
+ },
+ ], __FILE__, __LINE__, undef, time()
+ );
+ }
}
}
else {
@@ -11,6 +11,7 @@ use strict;
use Test::More;
if ($^O eq "MSWin32") {
+ plan skip_all => "Perl can't handle these tests on $^O - it crashes";
eval 'use Win32::Console';
if ($@) {
plan skip_all => "Win32::Console is required on $^O - try ActivePerl";
@@ -37,6 +38,7 @@ foreach my $die_on_bad_exit ( 0, 1 ) {
inline_states => {
stdout => sub { },
stdin => sub { },
+ _parent => sub { },
_start => sub {
my ( $kernel, $session, $heap ) = @_[KERNEL, SESSION, HEAP];
@@ -88,6 +90,7 @@ foreach my $die_on_bad_exit ( 0, 1 ) {
},
_stop => sub { },
_child => sub { },
+ _parent => sub { },
},
);
@@ -1,84 +0,0 @@
-#!/usr/bin/perl -w
-# $Id$
-# vim: filetype=perl
-
-# Welcome to recursive signals, this test makes sure that the signal
-# bookkeeping variables are not mucked up by recursion.
-
-use strict;
-
-sub POE::Kernel::TRACE_DEFAULT () { 1 }
-sub POE::Kernel::ASSERT_DEFAULT () { 1 }
-sub POE::Kernel::TRACE_FILENAME () { "./test-output.err" }
-
-use POE;
-
-use Test::More tests => 8;
-
-# The following session checks to make sure that sig_handled on an inner
-# signal doesn't make the kernel believe that the outer signal has been handled.
-
-my $i = 0;
-
-POE::Session->create(
- inline_states => {
- _start => sub {
- ok( ++$i == 1, "Session startup" );
- $_[KERNEL]->sig( 'HUP', 'hup' );
- $_[KERNEL]->sig( 'DIE', 'death' );
- $_[KERNEL]->signal( $_[SESSION], 'HUP' );
- $_[KERNEL]->yield( 'bad' );
- },
- bad => sub {
- fail( "We shouldn't get here" );
- },
- hup => sub {
- ok( ++$i == 2, "HUP handler" );
- my $foo = undef;
- $foo->put(); # oh my!
- },
- death => sub {
- ok( ++$i == 3, "DIE handler" );
- $_[KERNEL]->sig_handled();
- },
- _stop => sub {
- ok( ++$i == 4, "Session shutdown" );
- },
- },
-);
-
-# The following session checks to make sure that a nonmaskable signal is
-# not downgraded to a terminal signal.
-
-my $j = 0;
-
-POE::Session->create(
- inline_states => {
- _start => sub {
- ok( ++$j == 1, "Second session startup" );
- $_[KERNEL]->sig( 'ZOMBIE', 'zombie' );
- $_[KERNEL]->sig( 'DIE', 'death' );
- $_[KERNEL]->signal( $_[SESSION], 'ZOMBIE' );
- $_[KERNEL]->yield( 'bad' );
- },
- bad => sub {
- fail( "We shouldn't get here" );
- },
- zombie => sub {
- ok( ++$j == 2, "Zombie handler" );
- $_[KERNEL]->sig_handled(); # handling this should still die
- my $foo = undef;
- $foo->put(); # oh my!
- },
- death => sub {
- ok( ++$j == 3, "DIE handler" );
- $_[KERNEL]->sig_handled();
- },
- _stop => sub {
- ok( ++$j == 4, "Second session shutdown" );
- },
- },
-);
-
-
-POE::Kernel->run();
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+# $Id$
+
+use strict;
+use warnings;
+
+sub POE::Kernel::ASSERT_DEFAULT () { 1 }
+
+use Test::More tests => 2;
+use POE;
+
+POE::Session->create(
+ inline_states => {
+ _start => sub {
+ $poe_kernel->sig(DIE => 'parent_exception');
+ POE::Session->create(
+ inline_states => {
+ _start => sub {
+ $poe_kernel->sig(DIE => 'child_exception');
+ $poe_kernel->yield("throw_exception");
+ },
+ throw_exception => sub { die "goodbye sweet world" },
+ child_exception => sub { pass("child got exception") },
+ _stop => sub { },
+ },
+ )
+ },
+ parent_exception => sub {
+ pass("parent got exception");
+ $poe_kernel->sig_handled();
+ },
+ _stop => sub { },
+ _child => sub { },
+ },
+);
+
+POE::Kernel->run();
+exit;
+
+

0 comments on commit da8b9b9

Please sign in to comment.