/lib/POE/Test/Loops/k_sig_child.pm

https://github.com/rcaputo/poe-test-loops · Perl · 211 lines · 125 code · 48 blank · 38 comment · 15 complexity · c5aa7d1dfb0a4f1abd3ffa2b49ad2c01 MD5 · raw file

  1. #!/usr/bin/perl -w
  2. # vim: ts=2 sw=2 expandtab
  3. # Tests various signals using POE's stock signal handlers. These are
  4. # plain Perl signals, so mileage may vary.
  5. use strict;
  6. use lib qw(./mylib ../mylib);
  7. use Test::More;
  8. sub POE::Kernel::ASSERT_DEFAULT () { 1 }
  9. BEGIN {
  10. package
  11. POE::Kernel;
  12. use constant TRACE_DEFAULT => exists($INC{'Devel/Cover.pm'});
  13. }
  14. # This is the number of processes to fork. Increase this number if
  15. # your system can handle the resource use. Also try increasing it if
  16. # you suspect a problem with POE's SIGCHLD handling. Be warned
  17. # though: setting this too high can cause timing problems and test
  18. # failures on some systems.
  19. use constant FORK_COUNT => 8;
  20. BEGIN {
  21. # We can't "plan skip_all" because that calls exit(). And Tk will
  22. # croak if you call BEGIN { exit() }. And that croak will cause
  23. # this test to FAIL instead of skip.
  24. my $error;
  25. if ($^O eq "MSWin32" and not $ENV{POE_DANTIC}) {
  26. $error = "$^O does not support signals";
  27. }
  28. elsif ($^O eq "MacOS" and not $ENV{POE_DANTIC}) {
  29. $error = "$^O does not support fork";
  30. }
  31. if ($error) {
  32. print "1..0 # Skip $error\n";
  33. CORE::exit();
  34. }
  35. plan tests => FORK_COUNT+ 7;
  36. }
  37. use IO::Pipely qw(pipely);
  38. my ($pipe_read, $pipe_write) = pipely();
  39. BEGIN { use_ok("POE") }
  40. # Set up a second session that watches for child signals. This is to
  41. # test whether a session with only sig_child() stays alive because of
  42. # the signals.
  43. POE::Session->create(
  44. inline_states => {
  45. _start => sub { $_[KERNEL]->alias_set("catcher") },
  46. catch => sub {
  47. my ($kernel, $heap, $pid) = @_[KERNEL, HEAP, ARG0];
  48. $kernel->sig(CHLD => "got_sigchld");
  49. $kernel->sig_child($pid, "got_chld");
  50. $heap->{children}{$pid} = 1;
  51. $heap->{watched}++;
  52. },
  53. remove_alias => sub { $_[KERNEL]->alias_remove("catcher") },
  54. got_chld => sub {
  55. my ($heap, $pid) = @_[HEAP, ARG1];
  56. ok(delete($heap->{children}{$pid}), "caught SIGCHLD for watched pid $pid");
  57. $heap->{caught}++;
  58. },
  59. got_sigchld => sub {
  60. $_[HEAP]->{caught_sigchld}++;
  61. },
  62. _stop => sub {
  63. my $heap = $_[HEAP];
  64. ok(
  65. $heap->{watched} == $heap->{caught},
  66. "expected $heap->{watched} reaped children, got $heap->{caught}"
  67. );
  68. ok(
  69. $heap->{watched} == $heap->{caught_sigchld},
  70. "expected $heap->{watched} sig(CHLD), got $heap->{caught_sigchld}"
  71. );
  72. ok(!keys(%{$heap->{children}}), "all reaped children were watched");
  73. },
  74. },
  75. );
  76. # Set up a signal catching session. This test uses plain fork(2) and
  77. # POE's $SIG{CHLD} handler.
  78. POE::Session->create(
  79. inline_states => {
  80. _start => sub {
  81. my ($kernel, $heap) = @_[KERNEL, HEAP];
  82. # Clear the status counters, and catch SIGCHLD.
  83. $heap->{forked} = $heap->{reaped} = 0;
  84. # Fork some child processes, all to exit at the same time.
  85. my $fork_start_time = time();
  86. for (my $child = 0; $child < FORK_COUNT; $child++) {
  87. my $child_pid = fork;
  88. if (defined $child_pid) {
  89. if ($child_pid) {
  90. # Parent side keeps track of child IDs.
  91. $heap->{forked}++;
  92. $heap->{children}{$child_pid} = 1;
  93. $kernel->sig_child($child_pid, "catch_sigchld");
  94. $kernel->post(catcher => catch => $child_pid);
  95. }
  96. else {
  97. # A brief sleep so the parent has more opportunity to
  98. # finish forking.
  99. sleep 1;
  100. # Defensively make sure SIGINT will be fatal.
  101. $SIG{INT} = 'DEFAULT';
  102. # Tell the parent we're ready.
  103. print $pipe_write "$$\n";
  104. # Wait for SIGINT.
  105. sleep 3600;
  106. exit;
  107. }
  108. }
  109. else {
  110. die "fork error: $!";
  111. }
  112. }
  113. ok(
  114. $heap->{forked} == FORK_COUNT,
  115. "forked $heap->{forked} processes (out of " . FORK_COUNT . ")"
  116. );
  117. # NOTE: This is bad form. We're going to block here until all
  118. # children check in, or die trying.
  119. my $ready_count = 0;
  120. while (<$pipe_read>) {
  121. last if ++$ready_count >= FORK_COUNT;
  122. }
  123. $kernel->yield( 'forking_time_is_up' );
  124. },
  125. _stop => sub {
  126. my $heap = $_[HEAP];
  127. # Everything is done. See whether it succeeded.
  128. ok(
  129. $heap->{reaped} == $heap->{forked},
  130. "reaped $heap->{reaped} processes (out of $heap->{forked})"
  131. );
  132. },
  133. catch_sigchld => sub {
  134. my ($kernel, $heap) = @_[KERNEL, HEAP];
  135. # Count the child reap. If that's all of them, wait just a
  136. # little longer to make sure there aren't extra ones.
  137. if (++$heap->{reaped} >= FORK_COUNT) {
  138. $kernel->delay( reaping_time_is_up => 0.500 );
  139. }
  140. },
  141. forking_time_is_up => sub {
  142. my ($kernel, $heap) = @_[KERNEL, HEAP];
  143. # Forking time is over. We kill all the child processes as
  144. # immediately as possible.
  145. my $kill_count = kill INT => keys(%{$heap->{children}});
  146. ok(
  147. $kill_count == $heap->{forked},
  148. "killed $kill_count processes (out of $heap->{forked})"
  149. );
  150. # Start the reap timer. This will tell us how long to wait
  151. # between CHLD signals.
  152. $heap->{reap_start} = time();
  153. # Cap the maximum time for failures.
  154. $kernel->delay( reaping_time_is_up => 10 );
  155. },
  156. # Do nothing here. The timer exists just to keep the session
  157. # alive. Once it's dispatched, the session can exit.
  158. reaping_time_is_up => sub { undef },
  159. },
  160. );
  161. # Run the tests.
  162. POE::Kernel->run();
  163. 1;