[Chicago-talk] Speaking of threading

Steven Lembark lembark at wrkhors.com
Sat Dec 6 16:45:44 CST 2003

-- "Scott T. Hildreth" <shild at sbcglobal.net>

> On 05-Dec-2003 Chris McAvoy wrote:
>> You know, if anyone ever wants to do a talk on threading or forking perl,
>   ...and Steve can add to his talk by adding debugging fork() with
> $DB::fork_TTY,      unless he already did.  Unfortunately I could not
> make it.
>> I'd be very interested.

Lets say you debug something that forks. Without any preparation
for it you'll get:

main::(-e:1):   0
  DB<1> fork

######### Forked, but do not know how to create a new TTY. #########
  Since two debuggers fight for the same TTY, input is severely entangled.

  I know how to switch the output to a different window in xterms
  and OS/2 consoles only.  For a manual switch, put the name of the created 
  in $DB::fork_TTY, or define a function DB::get_fork_TTY() returning this.

  On UNIX-like systems one can get the name of a TTY for the given window
  by typing tty, and disconnect the shell from TTY by sleep 1000000.

With X11 you can:

	1. open an xterm (whatever).
	2. xterm -e 'tty; sleep 1d';
	3. perl -d as usual.
	4. $DB::fork_TTY = '<tty output from step 2>';
	5. Do whatever you like to debug the code.

A slightly cleaner solution is to add one item into your
#! code:

	sub DB::get_fork_TTY { $ENV{fork_TTY} }

now you can:

	1. open an xterm (whatever).
	2. xterm -e 'tty; sleep 1d';
	3. fork_TTY='<tty output>' perl -d foobar;

So far as forks themlselves, a fork just creates a mostly-duplicate
of the current process context. At the code level, the parent gets
back a childs PID for a successful fork, the child gets back zero
from the fork call, and failures return undef:

	if( (my $pid = fork) > 0 )
		# parent, this can wait, keep going, whatever.
	elsif( defined $pid )
		# child


		exit $status;
		confess "Phorkaphobia: $!"

Main thing is to make REAL DAMN SURE THE CHILD EXITS or you'll
probably cause phorkatosis, wherein the main loop keeps forking
children that fork children that fork chidren that... eventually
slurp up your symbol table and bring the SysAdmins breathing fire
at you :-)

If you want to just discharge the child proc's and don't really
care what they do then setting $SIG{CHLD} = 'IGNORE' will cause
the main code to reap the child proc's for you automically (instead
of leaving zombies around). This is described in perldoc perlipc if
you search for "CHLD".

The "wait" function is used to wait for child exits:

   wait    Behaves like the wait(2) system call on your system: it waits 
for a child process to terminate and
		   returns the pid of the deceased process, or "-1" if there are no child 
processes.  The status is
		   returned in $?.  Note that a return value of "-1" could mean that 
child processes are being automati-
		   cally reaped, as described in perlipc.

If you care what happend to the child proc's you can use wait to
block until something exits:

	my $logpath = "$logdir/blah";

	if( (my $pid = fork) > 0 )
		$childmeta{$pid} = { logpath => $logpath };
	elsif( defined $pid )
		open STDOUT, '>', $logpath
			or die "$logpath: $!";	



	# block on sigchld, basically.
	# wait hands back the child process

	if( (my $pid = wait) > 0 )
		if( $? )
			# bad news, boss...

			if( my $status = $? >> 8 )
				carp "exit( $status ) by $pid";
			elsif( my $signal = $? & 0xFF )
				carp "kill SIG-$signal on $pid";
			# don't leave around log files for completed jobs
			# unless they contain something or the job exited
			# nonzero.

			my $path = $childmeta{$pid}->{logpath}
				or die "Bogus childmeta: no data for $pid";

			unlink $path unless -s $path;
		print "All child proc's exited...";

		exit 0;

This is more-or-less what happens in Schedule::Parallel, which
keeps a fixed number of jobs running in parallel:

	my @jobz = foo_hook;

	my $jobcount = bar_hook;

	forkify splice @jobz, 0, $jobcount;

	while( (my $pid = wait) > 0 )
		forkify shift @jobz if @jobz;

The if-block on fork is basically all that forkify does.
Pre-forking however many jobs are supposed to run in parallel
leaves wait blocking on the first job that exits. When that
happens a new job is forked off if there is anything left to
do. Then @jobs is exhausted the loop waits for the last few
jobs to finish and falls out of the loop when wait returns
-1 (no jobs to wait for).

The "system" function is basically a fork/exec/wait all wrapped
in a nice, neat bundle for you so that you don't have to deal with
this stuff, just the return values.

Steven Lembark                               2930 W. Palmer
Workhorse Computing                       Chicago, IL 60647
                                            +1 888 359 3508

More information about the Chicago-talk mailing list