Skip to content

Commit 4848f7f

Browse files
committed
Make forking to report back result more elegant.
1 parent d67edf5 commit 4848f7f

File tree

1 file changed

+83
-57
lines changed

1 file changed

+83
-57
lines changed

judge/judgedaemon.main.php

Lines changed: 83 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function __construct()
7474
{
7575
self::$instance = $this;
7676

77-
$this->options = getopt("dv:n:hVe:j:t:", ["diskspace-error"]);
77+
$this->options = getopt("dv:n:hV", ["diskspace-error"]);
7878
if ($this->options === false) {
7979
echo "Error: parsing options failed.\n";
8080
$this->usage();
@@ -137,7 +137,7 @@ public function __construct()
137137
$runuser .= '-' . $this->options['daemonid'];
138138
}
139139

140-
if ($runuser === posix_getpwuid(posix_geteuid())['name'] ||
140+
if ($runuser === posix_getpwuid(posix_geteuid())['name'] ||
141141
RUNGROUP === posix_getgrgid(posix_getegid())['name']
142142
) {
143143
error("Do not run the judgedaemon as the runuser or rungroup.");
@@ -197,41 +197,7 @@ public function __construct()
197197

198198
$this->initsignals();
199199

200-
$this->read_credentials();
201-
202-
if (!empty($this->options['e'])) {
203-
$this->endpointID = $this->options['e'];
204-
$endpoint = $this->endpoints[$this->endpointID];
205-
$this->endpoints[$this->endpointID]['ch'] = $this->setup_curl_handle($endpoint['user'], $endpoint['pass']);
206-
$new_judging_run = (array)dj_json_decode(base64_decode(file_get_contents($this->options['j'])));
207-
$judgeTaskId = $this->options['t'];
208-
209-
$success = false;
210-
for ($i = 0; $i < 5; $i++) {
211-
if ($i > 0) {
212-
$sleep_ms = 100 + random_int(200, ($i + 1) * 1000);
213-
dj_sleep(0.001 * $sleep_ms);
214-
}
215-
$response = $this->request(
216-
sprintf('judgehosts/add-judging-run/%s/%s', $new_judging_run['hostname'],
217-
urlencode((string)$judgeTaskId)),
218-
'POST',
219-
$new_judging_run,
220-
false
221-
);
222-
if ($response !== null) {
223-
logmsg(LOG_DEBUG, "Adding judging run result for jt$judgeTaskId successful.");
224-
$success = true;
225-
break;
226-
}
227-
logmsg(LOG_WARNING, "Failed to report jt$judgeTaskId in attempt #" . ($i + 1) . ".");
228-
}
229-
if (!$success) {
230-
error("Final attempt of uploading jt$judgeTaskId was unsuccessful, giving up.");
231-
}
232-
unlink($this->options['j']);
233-
exit(0);
234-
}
200+
$this->readCredentials();
235201
}
236202

237203
public function run(): void
@@ -314,6 +280,12 @@ private function loop(): void
314280
if (function_exists('pcntl_signal_dispatch')) {
315281
pcntl_signal_dispatch();
316282
}
283+
if (function_exists('pcntl_waitpid')) {
284+
// Reap any finished child processes.
285+
while (pcntl_waitpid(-1, $status, WNOHANG) > 0) {
286+
// Do nothing.
287+
}
288+
}
317289
if ($this->exitsignalled) {
318290
logmsg(LOG_NOTICE, "Received signal, exiting.");
319291
$this->close_curl_handles();
@@ -647,7 +619,7 @@ private function judging_directory(string $workdirpath, array $judgeTask): strin
647619
. $judgeTask['jobid'];
648620
}
649621

650-
private function read_credentials(): void
622+
private function readCredentials(): void
651623
{
652624
$credfile = ETCDIR . '/restapi.secret';
653625
if (!is_readable($credfile)) {
@@ -1127,7 +1099,7 @@ private function registerJudgehost(): void
11271099
foreach ($unfinished as $jud) {
11281100
$workdir = $this->judging_directory($workdirpath, $jud);
11291101
@chmod($workdir, 0700);
1130-
logmsg(LOG_WARNING, "Found unfinished judging with jobid " . $jud['jobid'] .
1102+
logmsg(LOG_WARNING, "Found unfinished judging with jobid " . $jud['jobid'] .
11311103
" in my name; given back unfinished runs from me.");
11321104
}
11331105
}
@@ -1655,27 +1627,12 @@ private function run_testcase(
16551627

16561628
$ret = true;
16571629
if ($result === 'correct') {
1658-
// Post result back asynchronously. PHP is lacking multi-threading, so
1659-
// we just call ourselves again.
1660-
$tmpfile = tempnam(TMPDIR, 'judging_run_');
1661-
file_put_contents($tmpfile, base64_encode(dj_json_encode($new_judging_run)));
1662-
$judgedaemon = BINDIR . '/judgedaemon';
1663-
$cmd = $judgedaemon
1664-
. ' -e ' . $this->endpointID
1665-
. ' -t ' . $judgeTask['judgetaskid']
1666-
. ' -j ' . $tmpfile
1667-
. ' >> /dev/null & ';
1668-
shell_exec($cmd);
1630+
// Correct results get reported asynchronously, so we can continue judging in parallel.
1631+
$this->reportJudgingRun($judgeTask, $new_judging_run, asynchronous: true);
16691632
} else {
16701633
// This run was incorrect, only continue with the remaining judge tasks
16711634
// if we are told to do so.
1672-
$needsMoreWork = $this->request(
1673-
sprintf('judgehosts/add-judging-run/%s/%s', urlencode($this->myhost),
1674-
urlencode((string)$judgeTask['judgetaskid'])),
1675-
'POST',
1676-
$new_judging_run,
1677-
false
1678-
);
1635+
$needsMoreWork = $this->reportJudgingRun($judgeTask, $new_judging_run, asynchronous: false);
16791636
$ret = (bool)$needsMoreWork;
16801637
}
16811638

@@ -1689,6 +1646,75 @@ private function run_testcase(
16891646
return $ret;
16901647
}
16911648

1649+
private function reportJudgingRun(array $judgeTask, array $new_judging_run, bool $asynchronous): ?string
1650+
{
1651+
$judgeTaskId = $judgeTask['judgetaskid'];
1652+
1653+
if ($asynchronous && function_exists('pcntl_fork')) {
1654+
$pid = pcntl_fork();
1655+
if ($pid === -1) {
1656+
logmsg(LOG_WARNING, "Could not fork to report result for jt$judgeTaskId asynchronously, reporting synchronously.");
1657+
// Fallback to synchronous reporting by continuing in this process.
1658+
} elseif ($pid > 0) {
1659+
// Parent process, nothing more to do here.
1660+
logmsg(LOG_DEBUG, "Forked a child with PID $pid to report judging run for jt$judgeTaskId.");
1661+
return null;
1662+
} else {
1663+
// Child process: reset signal handlers to default.
1664+
pcntl_signal(SIGTERM, SIG_DFL);
1665+
pcntl_signal(SIGINT, SIG_DFL);
1666+
pcntl_signal(SIGHUP, SIG_DFL);
1667+
pcntl_signal(SIGUSR1, SIG_DFL);
1668+
1669+
// The child should use its own curl handle to avoid issues with sharing handles
1670+
// between processes.
1671+
$endpoint = $this->endpoints[$this->endpointID];
1672+
$this->endpoints[$this->endpointID]['ch'] = $this->setup_curl_handle($endpoint['user'], $endpoint['pass']);
1673+
}
1674+
} elseif ($asynchronous) {
1675+
logmsg(LOG_WARNING, "pcntl extension not available, reporting result for jt$judgeTaskId synchronously.");
1676+
}
1677+
1678+
$isChild = isset($pid) && $pid === 0;
1679+
1680+
$success = false;
1681+
for ($i = 0; $i < 5; $i++) {
1682+
if ($i > 0) {
1683+
$sleep_ms = 100 + random_int(200, ($i + 1) * 1000);
1684+
dj_sleep(0.001 * $sleep_ms);
1685+
}
1686+
$response = $this->request(
1687+
sprintf('judgehosts/add-judging-run/%s/%s', $new_judging_run['hostname'],
1688+
urlencode((string)$judgeTaskId)),
1689+
'POST',
1690+
$new_judging_run,
1691+
false
1692+
);
1693+
if ($response !== null) {
1694+
logmsg(LOG_DEBUG, "Adding judging run result for jt$judgeTaskId successful.");
1695+
$success = true;
1696+
break;
1697+
}
1698+
logmsg(LOG_WARNING, "Failed to report jt$judgeTaskId in attempt #" . ($i + 1) . ".");
1699+
}
1700+
1701+
if (!$success) {
1702+
$message = "Final attempt of uploading jt$judgeTaskId was unsuccessful, giving up.";
1703+
if ($isChild) {
1704+
error($message);
1705+
} else {
1706+
warning($message);
1707+
return null;
1708+
}
1709+
}
1710+
1711+
if ($isChild) {
1712+
exit(0);
1713+
}
1714+
1715+
return $response;
1716+
}
1717+
16921718
private function fetchTestcase(string $workdirpath, string $testcase_id, int $judgetaskid, string $testcase_hash): ?array
16931719
{
16941720
// Get both in- and output files, only if we didn't have them already.

0 commit comments

Comments
 (0)