#!/usr/bin/perl -w
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
#NAME
#  pctl - Multi-Process ConTroLler
#
#SYNOPSIS
#  pctl -m<M> -n<N> command ...
#
# (Actually, I now do all these in the initialization below.)
#
#REQUIRES
#
#DESCRIPTION
#  This is a demo program for a simple controller for multiple processes.  We
#  start  N copies of the command, and when one dies, we start another, until
#  we have started M processes and all have exited. We have defaults that may
#  change for testing different cases.
#
#OPTIONS
#
#  -m<M> is the maximum number of processes to start.
#
#  -n<N> is the max number of processes to have alive at the same time.
#
#REQUIRES
#  We call the program "sleeper" as the default, which is a shell script that
#  just does:
#    echo Process $$ sleeps for $1 seconds ...
#    sleep $1
#  Note that sleeper may output a number higher than the pid that we get from
#  the system command.
#
#EXAMPLES
#
#FILES
#
#BUGS
#
#SEE ALSO
#
#AUTHOR
#  John Chambers <jc@trillian.mit.edu>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

$| = 1;
$exitstat = 0;
($P = $0) =~ s".*/"";
$V = $ENV{"V_$P"} || $ENV{"D_$P"} || 2;	# Verbose level.

$cmd = 'sleeper';	# The command to run in each process
$lastpid  = 0;		# The pid of the last process started
$maxsleep = 20;		# Max param to pass to the command
$maxprocs = 5;		# Default max processes to start
$numprocs = 3;		# Default number of processes at the same time
$pstarted = 0;		# Number of processes we've started so far
$pdone    = 0;		# Number of processes that have finished

@pids = ();		# Table of process ids
@cmds = ();		# Table of commands in each slot
%p2i  = ();		# Converts proccess id to slot number
@pinit = ();	# When a process started

while ($p = wait) {
	if ($p < 0) {		# No processes alive (yet)
		print "\n$P: No processes alive.\n" if $V>0;
		if ($pdone >= $maxprocs) {
			print "$P: $pdone of $maxprocs done; exiting.\n" if $V>1;
			exit ($pdone - $maxprocs);	# Nonzero exit if too many finished
		}
		for ($s=0; $s<$numprocs; ++$s) {	# Start up $numprocs processes
			++$pstarted;	# Count the processes that we (attempt to) start
			startproc($s,$cmd);
		#	sleep 1;
		}
		print "$P: Started $pstarted initial processes.\n" if $V>1;
	} elsif ($p == 0) {	# "There are processes running" [on some OSs]
		print "\n$P: wait returned zero.\n" if $V>0;
	} else {			# A specific process just died
		print "\n";
		print "$P: Process $p finished.\n" if $V>2;
		++$pdone;		# Count the processes that finish.
		$s = $p2i[$p];
		$ptime = time - $pinit[$s];	# How many seconds the process lived
		print "$P: Process $p lived $ptime sec in slot $s: \"" . $cmds[$s] . "\"\n" if $V>1;
		if ($pstarted < $maxprocs) {
			print "$P: Start a new process in slot $s ...\n" if $V>1;
			++$pstarted;		# Count the processes that we (attempt to) start
			startproc($s,$cmd);
		} else {
			print "$P: $pstarted of $maxprocs started; $pdone done; waiting for rest to finish ...\n" if $V>1;
		}
	}
}

print "$P: Exit with status $exitstat.\n" if $V>1;
exit $exitstat;

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #

sub startproc {my $F='startproc';
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	local($slot,$cmd) = @_;
	local($pid);
	print "$F: Slot $slot cmd \"$cmd\"\n" if $V>2;
	if ($cmd eq 'sleeper') {	# Special test case just sleeps for N seconds
		$cmd .= ' ' . ((($lastpid + time + 17) % $maxsleep) + 1);
		print "$F: Slot $slot cmd \"$cmd\"\n" if $V>2;
	}
	if ($pid = fork) {	# Parent
		if ($pid < 0) {
			print "$F: ### Can't start a new process [$!]\n" if $V>0;
		} else {
			print "$F: Started process $pid slot $slot \"$cmd\"\n" if $V>1;
			$pids[$slot]  = $pid;
			$cmds[$slot]  = $cmd;
			$p2i[$pid]    = $slot;
			$pinit[$slot] = time;	# Note when we start the process
			$lastpid = $pid;
		}
	} else {			# Child (new process)
		system $cmd;
		print "\n$F: Child process $$ exiting.\n" if $V>1;
		exit 0;
	}
}
