@@ -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