<?php

define('USAGE', 'php %s channels tasks');

$progname=basename($argv[0]);

function abort($msg, $code=1) {
	echo $msg, PHP_EOL;
	exit($code);
}

function usage() {
	global $progname;

	abort(sprintf(USAGE, $progname), 1);
}

if ($argc != 3) {
	usage();
}

$nchannels=$argv[1];
if (!is_numeric($nchannels) or $nchannels < 1) {
	abort($nchannels . '?');
}

$ntasks=$argv[2];
if (!is_numeric($ntasks) or $ntasks < 0) {
	abort($ntasks . '?');
}

declare(ticks = 1);

define('MINTIME', 0.2);
define('MAXTIME', 1.0);

define('TIMESLICE', 1);

$feeders=array();
$atwork=array();

pcntl_signal(SIGCHLD,	'sig_handler');

pcntl_signal(SIGINT,	'sig_handler');
pcntl_signal(SIGTERM,	'sig_handler');

pcntl_signal(SIGHUP,	'sig_handler');

pcntl_signal(SIGQUIT,	SIG_IGN);

for (;;) {
	for ($channel=0; $channel < $nchannels; $channel++) {
		if (!$ntasks) {
			break;
		}

		if (isset($atwork[$channel])) {
			continue;
		}

		if (isset($feeders[$channel]) and $feeders[$channel] > microtime(true)) {
			continue;
		}

		$pid = pcntl_fork();

		if ($pid === -1) {
			terminate(2);
		}
		else if ($pid === 0) {
			run_task($channel);
			exit(0);
		}
		else {
			$atwork[$channel]=$pid;

			$feeders[$channel]=microtime(true)+TIMESLICE;

			--$ntasks;
		}
	}

	if (!count($atwork)) {
		if (!$ntasks) {
			terminate();
		}

		$dtime=min($feeders)-microtime(true);
		$msecs=ceil($dtime*1000000);
		usleep($msecs);
		continue;
	}

	$resting=array_diff_key($feeders, array_values($atwork));
	$dtime=$resting ? min($resting)-microtime(true) : 0;

	if ($dtime > 0) {
		$secs=floor($dtime);
		$nano=(int)(($dtime-$secs)*1000000000);
		@pcntl_sigtimedwait(array(SIGCHLD), $siginfo, $secs, $nano);
	}
	else {
		@pcntl_sigwaitinfo(array(SIGCHLD));
	}

	while (($pid = pcntl_wait($status, WNOHANG)) > 0) {
		if (pcntl_wifexited($status)) {
			$retcode = pcntl_wexitstatus($status);
			if ($retcode !== 0) {
				;
			}
			$channel=array_search($pid, $atwork);
			unset($atwork[$channel]);
		}
	}
}

function terminate($code=0) {
	exit($code);
}

function sig_handler($signo) {
	switch ($signo) {
		case SIGCHLD:
			break;
		case SIGHUP:
			break;
		case SIGINT:
		case SIGTERM:
			terminate(1);
		default:
			break;
	}
}

function run_task($channel) {
	global $ntasks;

	$ts=date('h:i:s');
	$secs=rand(MINTIME*10, MAXTIME*10) / 10;

	$s=sprintf("%4d %2d %s %0.1f", $ntasks, $channel+1, $ts, $secs);
	echo "$s\n";

	$msecs=$secs * 1000000;
	usleep($msecs);
}
