Skip to content

Commit 45d6ebb

Browse files
committed
Lock, and transaction-free entry_point update
1 parent d4762d2 commit 45d6ebb

File tree

1 file changed

+71
-14
lines changed

1 file changed

+71
-14
lines changed

webapp/src/Controller/API/JudgehostController.php

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Doctrine\DBAL\ArrayParameterType;
3333
use Doctrine\DBAL\Exception;
3434
use Doctrine\DBAL\Exception as DBALException;
35+
use Doctrine\DBAL\TransactionIsolationLevel;
3536
use Doctrine\ORM\AbstractQuery;
3637
use Doctrine\ORM\EntityManager;
3738
use Doctrine\ORM\EntityManagerInterface;
@@ -313,33 +314,89 @@ public function updateJudgingAction(
313314
}
314315

315316
if ($request->request->has('output_compile')) {
317+
$output_compile = base64_decode($request->request->get('output_compile'));
318+
316319
// Note: we use ->get here instead of ->has since entry_point can be the empty string and then we do not
317320
// want to update the submission or send out an update event
318321
if ($request->request->get('entry_point')) {
319-
$this->em->wrapInTransaction(function () use ($query, $request, &$judging) {
320-
$submission = $judging->getSubmission();
321-
if ($submission->getEntryPoint() === $request->request->get('entry_point')) {
322-
return;
322+
// Lock-free setting of, and detection of mismatched entry_point.
323+
$submission = $judging->getSubmission();
324+
325+
// Retrieve, and update the current entrypoint.
326+
$oldEntryPoint = $submission->getEntryPoint();
327+
$newEntryPoint = $request->request->get('entry_point');
328+
329+
330+
if ($oldEntryPoint === $newEntryPoint) {
331+
// Nothing to do
332+
} elseif (!empty($oldEntryPoint)) {
333+
// Conflict detected disable the judgehost.
334+
$disabled = [
335+
'kind' => 'judgehost',
336+
'hostname' => $judgehost->getHostname(),
337+
];
338+
$error = new InternalError();
339+
$error
340+
->setJudging($judging)
341+
->setContest($judging->getContest())
342+
->setDescription('Reported EntryPoint conflict difference for j' . $judging->getJudgingid().'. Expected: "' . $oldEntryPoint. '", received: "' . $newEntryPoint . '".')
343+
->setJudgehostlog(base64_encode('New compilation output: ' . $output_compile))
344+
->setTime(Utils::now())
345+
->setDisabled($disabled);
346+
$this->em->persist($error);
347+
} else {
348+
// Update needed. Note, conflicts might still be possible.
349+
350+
$rowsAffected = $this->em->createQueryBuilder()
351+
->update(Submission::class, 's')
352+
->set('s.entry_point', ':entrypoint')
353+
->andWhere('s.submitid = :id')
354+
->andWhere('s.entry_point IS NULL')
355+
->setParameter('entrypoint', $newEntryPoint)
356+
->setParameter('id', $submission->getSubmitid())
357+
->getQuery()
358+
->execute();
359+
360+
if ($rowsAffected == 0) {
361+
// There is a potential conflict, two options.
362+
// The new entry point is either the same (no issue) or different (conflict).
363+
// Read the entrypoint and check.
364+
$this->em->clear();
365+
$currentEntryPoint = $query->getOneOrNullResult()->getSubmission()->getEntryPoint();
366+
if ($newEntryPoint !== $currentEntryPoint) {
367+
// Conflict detected disable the judgehost.
368+
$disabled = [
369+
'kind' => 'judgehost',
370+
'hostname' => $judgehost->getHostname(),
371+
];
372+
$error = new InternalError();
373+
$error
374+
->setJudging($judging)
375+
->setContest($judging->getContest())
376+
->setDescription('Reported EntryPoint conflict difference for j' . $judging->getJudgingid().'. Expected: "' . $oldEntryPoint. '", received: "' . $newEntryPoint . '".')
377+
->setJudgehostlog(base64_encode('New compilation output: ' . $output_compile))
378+
->setTime(Utils::now())
379+
->setDisabled($disabled);
380+
$this->em->persist($error);
381+
}
382+
} else {
383+
$submissionId = $submission->getSubmitid();
384+
$contestId = $submission->getContest()->getCid();
385+
$this->eventLogService->log('submission', $submissionId,
386+
EventLogService::ACTION_UPDATE, $contestId);
323387
}
324-
$submission->setEntryPoint($request->request->get('entry_point'));
325-
$this->em->flush();
326-
$submissionId = $submission->getSubmitid();
327-
$contestId = $submission->getContest()->getCid();
328-
$this->eventLogService->log('submission', $submissionId,
329-
EventLogService::ACTION_UPDATE, $contestId);
330388

331-
// As EventLogService::log() will clear the entity manager, so the judging has
332-
// now become detached. We will have to reload it.
389+
// As EventLogService::log() will clear the entity manager, both branches clear the entity manager.
390+
// The judging is now detached, reload it.
333391
/** @var Judging $judging */
334392
$judging = $query->getOneOrNullResult();
335-
});
393+
}
336394
}
337395

338396
// Reload judgehost just in case it got cleared above.
339397
/** @var Judgehost $judgehost */
340398
$judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]);
341399

342-
$output_compile = base64_decode($request->request->get('output_compile'));
343400
if ($request->request->getBoolean('compile_success')) {
344401
if ($judging->getOutputCompile() === null) {
345402
$judging

0 commit comments

Comments
 (0)