@@ -72,7 +72,7 @@ public function __construct()
7272 {
7373 self ::$ instance = $ this ;
7474
75- $ this ->options = getopt ("dv:n:hVe:j:t: " , ["diskspace-error " ]);
75+ $ this ->options = getopt ("dv:n:hV " , ["diskspace-error " ]);
7676 if ($ this ->options === false ) {
7777 echo "Error: parsing options failed. \n" ;
7878 $ this ->usage ();
@@ -133,7 +133,7 @@ public function __construct()
133133 $ runuser .= '- ' . $ this ->options ['daemonid ' ];
134134 }
135135
136- if ($ runuser === posix_getpwuid (posix_geteuid ())['name ' ] ||
136+ if ($ runuser === posix_getpwuid (posix_geteuid ())['name ' ] ||
137137 RUNGROUP === posix_getgrgid (posix_getegid ())['name ' ]
138138 ) {
139139 error ("Do not run the judgedaemon as the runuser or rungroup. " );
@@ -195,39 +195,7 @@ public function __construct()
195195
196196 $ this ->read_credentials ();
197197
198- if (!empty ($ this ->options ['e ' ])) {
199- $ this ->endpointID = $ this ->options ['e ' ];
200- $ endpoint = $ this ->endpoints [$ this ->endpointID ];
201- $ this ->endpoints [$ this ->endpointID ]['ch ' ] = $ this ->setup_curl_handle ($ endpoint ['user ' ], $ endpoint ['pass ' ]);
202- $ new_judging_run = (array )dj_json_decode (base64_decode (file_get_contents ($ this ->options ['j ' ])));
203- $ judgeTaskId = $ this ->options ['t ' ];
204-
205- $ success = false ;
206- for ($ i = 0 ; $ i < 5 ; $ i ++) {
207- if ($ i > 0 ) {
208- $ sleep_ms = 100 + random_int (200 , ($ i + 1 ) * 1000 );
209- dj_sleep (0.001 * $ sleep_ms );
210- }
211- $ response = $ this ->request (
212- sprintf ('judgehosts/add-judging-run/%s/%s ' , $ new_judging_run ['hostname ' ],
213- urlencode ((string )$ judgeTaskId )),
214- 'POST ' ,
215- $ new_judging_run ,
216- false
217- );
218- if ($ response !== null ) {
219- logmsg (LOG_DEBUG , "Adding judging run result for jt $ judgeTaskId successful. " );
220- $ success = true ;
221- break ;
222- }
223- logmsg (LOG_WARNING , "Failed to report jt $ judgeTaskId in attempt # " . ($ i + 1 ) . ". " );
224- }
225- if (!$ success ) {
226- error ("Final attempt of uploading jt $ judgeTaskId was unsuccessful, giving up. " );
227- }
228- unlink ($ this ->options ['j ' ]);
229- exit (0 );
230- }
198+
231199 }
232200
233201 public function run (): void
@@ -310,6 +278,12 @@ private function loop(): void
310278 if (function_exists ('pcntl_signal_dispatch ' )) {
311279 pcntl_signal_dispatch ();
312280 }
281+ if (function_exists ('pcntl_waitpid ' )) {
282+ // Reap any finished child processes.
283+ while (pcntl_waitpid (-1 , $ status , WNOHANG ) > 0 ) {
284+ // Do nothing.
285+ }
286+ }
313287 if ($ this ->exitsignalled ) {
314288 logmsg (LOG_NOTICE , "Received signal, exiting. " );
315289 $ this ->close_curl_handles ();
@@ -1123,7 +1097,7 @@ private function registerJudgehost(): void
11231097 foreach ($ unfinished as $ jud ) {
11241098 $ workdir = $ this ->judging_directory ($ workdirpath , $ jud );
11251099 @chmod ($ workdir , 0700 );
1126- logmsg (LOG_WARNING , "Found unfinished judging with jobid " . $ jud ['jobid ' ] .
1100+ logmsg (LOG_WARNING , "Found unfinished judging with jobid " . $ jud ['jobid ' ] .
11271101 " in my name; given back unfinished runs from me. " );
11281102 }
11291103 }
@@ -1651,27 +1625,12 @@ private function run_testcase(
16511625
16521626 $ ret = true ;
16531627 if ($ result === 'correct ' ) {
1654- // Post result back asynchronously. PHP is lacking multi-threading, so
1655- // we just call ourselves again.
1656- $ tmpfile = tempnam (TMPDIR , 'judging_run_ ' );
1657- file_put_contents ($ tmpfile , base64_encode (dj_json_encode ($ new_judging_run )));
1658- $ judgedaemon = BINDIR . '/judgedaemon ' ;
1659- $ cmd = $ judgedaemon
1660- . ' -e ' . $ this ->endpointID
1661- . ' -t ' . $ judgeTask ['judgetaskid ' ]
1662- . ' -j ' . $ tmpfile
1663- . ' >> /dev/null & ' ;
1664- shell_exec ($ cmd );
1628+ // Correct results get reported asynchronously, so we can continue judging in parallel.
1629+ $ this ->reportJudgingRun ($ judgeTask , $ new_judging_run , asynchronous: true );
16651630 } else {
16661631 // This run was incorrect, only continue with the remaining judge tasks
16671632 // if we are told to do so.
1668- $ needsMoreWork = $ this ->request (
1669- sprintf ('judgehosts/add-judging-run/%s/%s ' , urlencode ($ this ->myhost ),
1670- urlencode ((string )$ judgeTask ['judgetaskid ' ])),
1671- 'POST ' ,
1672- $ new_judging_run ,
1673- false
1674- );
1633+ $ needsMoreWork = $ this ->reportJudgingRun ($ judgeTask , $ new_judging_run , asynchronous: false );
16751634 $ ret = (bool )$ needsMoreWork ;
16761635 }
16771636
@@ -1685,6 +1644,75 @@ private function run_testcase(
16851644 return $ ret ;
16861645 }
16871646
1647+ private function reportJudgingRun (array $ judgeTask , array $ new_judging_run , bool $ asynchronous ): ?string
1648+ {
1649+ $ judgeTaskId = $ judgeTask ['judgetaskid ' ];
1650+
1651+ if ($ asynchronous && function_exists ('pcntl_fork ' )) {
1652+ $ pid = pcntl_fork ();
1653+ if ($ pid === -1 ) {
1654+ logmsg (LOG_WARNING , "Could not fork to report result for jt $ judgeTaskId asynchronously, reporting synchronously. " );
1655+ // Fallback to synchronous reporting by continuing in this process.
1656+ } elseif ($ pid > 0 ) {
1657+ // Parent process, nothing more to do here.
1658+ logmsg (LOG_DEBUG , "Forked a child with PID $ pid to report judging run for jt $ judgeTaskId. " );
1659+ return null ;
1660+ } else {
1661+ // Child process: reset signal handlers to default.
1662+ pcntl_signal (SIGTERM , SIG_DFL );
1663+ pcntl_signal (SIGINT , SIG_DFL );
1664+ pcntl_signal (SIGHUP , SIG_DFL );
1665+ pcntl_signal (SIGUSR1 , SIG_DFL );
1666+
1667+ // The child should use its own curl handle to avoid issues with sharing handles
1668+ // between processes.
1669+ $ endpoint = $ this ->endpoints [$ this ->endpointID ];
1670+ $ this ->endpoints [$ this ->endpointID ]['ch ' ] = $ this ->setup_curl_handle ($ endpoint ['user ' ], $ endpoint ['pass ' ]);
1671+ }
1672+ } elseif ($ asynchronous ) {
1673+ logmsg (LOG_WARNING , "pcntl extension not available, reporting result for jt $ judgeTaskId synchronously. " );
1674+ }
1675+
1676+ $ isChild = isset ($ pid ) && $ pid === 0 ;
1677+
1678+ $ success = false ;
1679+ for ($ i = 0 ; $ i < 5 ; $ i ++) {
1680+ if ($ i > 0 ) {
1681+ $ sleep_ms = 100 + random_int (200 , ($ i + 1 ) * 1000 );
1682+ dj_sleep (0.001 * $ sleep_ms );
1683+ }
1684+ $ response = $ this ->request (
1685+ sprintf ('judgehosts/add-judging-run/%s/%s ' , $ new_judging_run ['hostname ' ],
1686+ urlencode ((string )$ judgeTaskId )),
1687+ 'POST ' ,
1688+ $ new_judging_run ,
1689+ false
1690+ );
1691+ if ($ response !== null ) {
1692+ logmsg (LOG_DEBUG , "Adding judging run result for jt $ judgeTaskId successful. " );
1693+ $ success = true ;
1694+ break ;
1695+ }
1696+ logmsg (LOG_WARNING , "Failed to report jt $ judgeTaskId in attempt # " . ($ i + 1 ) . ". " );
1697+ }
1698+
1699+ if (!$ success ) {
1700+ $ message = "Final attempt of uploading jt $ judgeTaskId was unsuccessful, giving up. " ;
1701+ if ($ isChild ) {
1702+ error ($ message );
1703+ } else {
1704+ warning ($ message );
1705+ return null ;
1706+ }
1707+ }
1708+
1709+ if ($ isChild ) {
1710+ exit (0 );
1711+ }
1712+
1713+ return $ response ;
1714+ }
1715+
16881716 private function fetchTestcase (string $ workdirpath , string $ testcase_id , int $ judgetaskid , string $ testcase_hash ): ?array
16891717 {
16901718 // Get both in- and output files, only if we didn't have them already.
0 commit comments