Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Greetings,

I'm trying to understand forking in perl. The following codes explodes in a cloud of children in spite of my attempts to limit them. What have I done wrong?

#!/usr/bin/perl use strict; use warnings; use Benchmark; my $count = 2; my $max_children = 5; my @array = ( 1 .. 10 ); my $x; sub straight { my $c=0; foreach $x (@array){ $c++; sleep 1; } print "straight c = $c\n"; }; sub fork { my $iterations=0; my $children=0; my $pid; foreach $x (@array) { $pid = fork; if ($pid == 0){ # Is a child $iterations++; sleep 1; } else { # fork returned new child pid $children++; $iterations++; sleep 1; if ( $children >= $max_children ){ wait; $children--; } } } if ($pid == 0){ # Is a child exit; } wait; print "interations = $iterations\n"; } timethese ( $count, { 'straight' => '&straight', 'fork' => '&fork' } ); exit;

Replies are listed 'Best First'.
Re: Help with Fork bomb
by Athanasius (Archbishop) on Aug 30, 2012 at 15:52 UTC

    On the first iteration of the foreach loop in sub fork (very unfortunate name, BTW!), built-in fork creates a child process which inherits the entire code, including the foreach loop.

    On the parent’s second iteration, it creates another child. Meanwhile, on the child’s first iteration, it also creates another child.

    On the next iteration, the parent and each existing child create more children. No wonder the code “explodes in a cloud of children.”

    You need to re-think the logic of this code!

    Athanasius <°(((><contra mundum

Re: Help with Fork bomb
by aitap (Curate) on Aug 30, 2012 at 15:53 UTC

    $pid = fork; if ($pid == 0){ # Is a child $iterations++; sleep 1; } ... } } if ($pid == 0){ # Is a child exit; }
    I think, the problem might be there: every child continues the loop after sleep 1 and creates more and more grandchildren. Placing exit after sleep fixed the issue for me: before after.

    By the way, separate shell with ulimit -u 300 was very useful for testing this code.

    Sorry if my advice was wrong.
      I think I've seen instances when exit in the child kills the parent. Is that possible? How does one defend against it?
            I think I've seen instances when exit in the child kills the parent

        Parricide? I suppose, but if you set up proper handling of SIGCHLD in your code and have a plan of what to do you should be OK.

        # ---- yadayadayada # # Handler for SIGCHLD $SIG{CHLD}=sub { # do something here } ;

        Peter L. Berghold -- Unix Professional
        Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
        In addition to the previous comment, by default, perl ignores SIGCHLD, so dead children remain as zombies and die only when parent dies. You can avoid it by handling SIGCHLD as specified above.
        Sorry if my advice was wrong.
Re: Help with Fork bomb
by blue_cowdawg (Monsignor) on Aug 30, 2012 at 18:00 UTC

    Here is a scaled down version of what you are trying to do in a fashion that will work...

    #!/usr/bin/perl -w use strict; $SIG{CHLD}=\&catchChildDeath; # Track how many children we have running our $children=0; # Limits on min and max for number of children to run my $min_child=5; my $max_child=10; # Enable or disable our ability to spawn my $spawnEnabled=1; # Run until killed while(1){ spawn(); sleep 5; } exit(0); # Deal with the death of a child. A sad event to be sure. sub catchChildDeath{ printf "Death of child noted.\n"; $children--; $children = ( $children < 0 ? 0 : $children); unless ($spawnEnabled){ $spawnEnabled = ( $children < $min_child ? 1 : 0 ); } printf "There are %d children running\n",$children; } # Spawn if we must, or not. sub spawn{ unless ( $spawnEnabled ) { if ($children <= $min_child ){ $spawnEnabled=1; } else { return unless $spawnEnabled; } } if ( fork()){ $children++; $spawnEnabled = 0 if $children > $max_child; printf "%d children running\n",$children; } else { kidPlay(); } } # This encapsulates the behavior of the child. sub kidPlay{ my $iter=0; printf "Child PID: %d\n",$$; while($iter++ < 10){ sleep 1; } printf "Child PID: %d exiting..",$$; exit(0); }
    Of particular note is the use of $SIG{CHLD} to catch the event of a child process exiting. Also the counters and limit values that I use to track what is going on.

    When I ran this it worked marvelously. Add to it what you want. Disclaimer: this code only minimally tested and is not to be used in conjunction with ICBM launchers, nuclear reactors or transporter rooms.


    Peter L. Berghold -- Unix Professional
    Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg

      Very interesting code. Thank you. Suppose I wanted to have the loop pass data to the child.

      my @array = (1 .. 20); foreach my $x (@array){ spawn($x); } ... sub spawn{ # the unless and : ? statements were too alien to me. my $x = shift; if ( ! $spawn_enabled ) { if ($children <= $min_child ){ $spawn_enabled=1; } elsif ( ! $spawn_enabled ) { return; } } if ( fork() ){ $children++; if ( $children > $max_child ){ $spawn_enabled = 0; } #printf "%d children running\n",$children; } else { my_child_work($x); } } sub my_child_work{ my $x = shift; print "child x $x\n"; sleep 1; exit; }

      This results is numbers being missed. In fact, I only see the numbers 1, 2 and 3 being printed when using a max of 2 children. I think this is due to my original logic flaw. I do not have the problem when using Parallel ForkManager but, I'd like to understand how to do without.

            Suppose I wanted to have the loop pass data to the child.

        Data is shared between parent/child at the time fork() happens. Once the child is running though that changes. Parent and child are then independent of one another.

        If you want a parent to communicate with the child after the fork there are a multitude of ways of doing that. Everything from duping STDIN,STDERR,STDOUT to using fifo devices to shared memory and semaphores. All depends on why you want the communication and how complicated you are willing to accomplish that goal.


        Peter L. Berghold -- Unix Professional
        Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
Re: Help with Fork bomb
by cursion (Pilgrim) on Aug 30, 2012 at 16:07 UTC

    Each fork() duplicates the code that is running. All the variables are duplicated and they are each in a separate process - and most importantly they don't communicate with each other (except for the wait() call). They don't update each other's variables. Each child is acting like like a parent when it starts the next iteration of the loop.

    The program you started is managing it's kids properly (limiting kids to 5). However, each child is also acting as a parent and managing kids. Their kids too. And so on.

    You start with one process, that makes two, then four, then eight, etc.

    This probably isn't what you intended ... you may want to check out a package by the name of Parallel::ForkManager. Find some sample code using that and you might find the light bulb.