From fb7c3ca395991f2a9f0fbe2c9bb18193d3764c5e Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Wed, 1 May 2024 16:16:15 +0200 Subject: [PATCH 1/2] Send less event updates for judgements in parallel mode. We used to send event updates for all testcases that came in after we already determined the final verdict for a problem. Now we check if we already have a previous end time for the judgement, and then we don't do this anymore. Note that race conditions can still make it such that we send multiple update events, where two judgehosts know the result at the same time and. However, this happens way less often. --- webapp/src/Controller/API/JudgehostController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 8eb6239515..4ae5fb48b4 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1023,9 +1023,11 @@ private function addSingleJudgingRun( if (!$hasNullResults || $lazyEval !== DOMJudgeService::EVAL_FULL) { // NOTE: setting endtime here determines in testcases_GET // whether a next testcase will be handed out. + $this->logger->error('Judging %d is done, setting endtime, it had %d', [ $judging->getJudgingid(), $judging->getEndtime() ]); + $this->logger->error('Verdict is %s', [$result]); + $sendJudgingEvent = !$judging->getEndtime(); $judging->setEndtime(Utils::now()); $this->maybeUpdateActiveJudging($judging); - $sendJudgingEvent = true; } $this->em->flush(); From 1e221b47fc66e39a0e22887d6795fb68a00ac321 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Fri, 22 Nov 2024 16:24:42 +0100 Subject: [PATCH 2/2] Only set end time when verdict is set. Also calculate max run time only based on those runs. --- webapp/migrations/Version20241122150726.php | 36 +++++++++++++++++++ .../Controller/API/JudgehostController.php | 23 +++++++++--- .../Controller/API/JudgementController.php | 8 ++--- .../src/DataTransferObject/JudgingWrapper.php | 10 ------ webapp/src/Entity/Judging.php | 30 ++++++++++++++++ 5 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 webapp/migrations/Version20241122150726.php diff --git a/webapp/migrations/Version20241122150726.php b/webapp/migrations/Version20241122150726.php new file mode 100644 index 0000000000..76de33e34e --- /dev/null +++ b/webapp/migrations/Version20241122150726.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE judging ADD max_runtime_for_verdict NUMERIC(32, 9) UNSIGNED DEFAULT NULL COMMENT \'The maximum run time for all runs that resulted in the verdict\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE judging DROP max_runtime_for_verdict'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 4ae5fb48b4..fd0c0fa6a2 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1023,10 +1023,25 @@ private function addSingleJudgingRun( if (!$hasNullResults || $lazyEval !== DOMJudgeService::EVAL_FULL) { // NOTE: setting endtime here determines in testcases_GET // whether a next testcase will be handed out. - $this->logger->error('Judging %d is done, setting endtime, it had %d', [ $judging->getJudgingid(), $judging->getEndtime() ]); - $this->logger->error('Verdict is %s', [$result]); - $sendJudgingEvent = !$judging->getEndtime(); - $judging->setEndtime(Utils::now()); + // We want to set the endtime and max runtime only once (once the verdict is known), + // so that the API doesn't update these values once they are set. + // We also don't want to send judging events after the verdict is known. + if (!$judging->getEndtime()) { + $sendJudgingEvent = true; + $judging->setEndtime(Utils::now()); + + // Also calculate the max run time and set it + $maxRunTime = $this->em->createQueryBuilder() + ->from(Judging::class, 'j') + ->select('MAX(jr.runtime) AS maxruntime') + ->leftJoin('j.runs', 'jr') + ->andWhere('j.judgingid = :judgingid') + ->andWhere('jr.runtime IS NOT NULL') + ->setParameter('judgingid', $judging->getJudgingid()) + ->getQuery() + ->getSingleScalarResult(); + $judging->setMaxRuntimeForVerdict($maxRunTime); + } $this->maybeUpdateActiveJudging($judging); } $this->em->flush(); diff --git a/webapp/src/Controller/API/JudgementController.php b/webapp/src/Controller/API/JudgementController.php index ca6530efbc..2b9d089d29 100644 --- a/webapp/src/Controller/API/JudgementController.php +++ b/webapp/src/Controller/API/JudgementController.php @@ -104,7 +104,7 @@ protected function getQueryBuilder(Request $request): QueryBuilder { $queryBuilder = $this->em->createQueryBuilder() ->from(Judging::class, 'j') - ->select('j, c, s, MAX(jr.runtime) AS maxruntime') + ->select('j, c, s') ->leftJoin('j.contest', 'c') ->leftJoin('j.submission', 's') ->leftJoin('j.rejudging', 'r') @@ -161,12 +161,10 @@ protected function getIdField(): string return 'j.judgingid'; } - public function transformObject($object): JudgingWrapper + public function transformObject($judging): JudgingWrapper { /** @var Judging $judging */ - $judging = $object[0]; - $maxRunTime = $object['maxruntime'] === null ? null : (float)$object['maxruntime']; $judgementTypeId = $judging->getResult() ? $this->verdicts[$judging->getResult()] : null; - return new JudgingWrapper($judging, $maxRunTime, $judgementTypeId); + return new JudgingWrapper($judging, $judgementTypeId); } } diff --git a/webapp/src/DataTransferObject/JudgingWrapper.php b/webapp/src/DataTransferObject/JudgingWrapper.php index 7b2f0b325c..ea16f4cd91 100644 --- a/webapp/src/DataTransferObject/JudgingWrapper.php +++ b/webapp/src/DataTransferObject/JudgingWrapper.php @@ -11,17 +11,7 @@ class JudgingWrapper public function __construct( #[Serializer\Inline] protected readonly Judging $judging, - #[Serializer\Exclude] - protected readonly ?float $maxRunTime = null, #[Serializer\SerializedName('judgement_type_id')] protected readonly ?string $judgementTypeId = null ) {} - - #[Serializer\VirtualProperty] - #[Serializer\SerializedName('max_run_time')] - #[Serializer\Type('float')] - public function getMaxRunTime(): ?float - { - return Utils::roundedFloat($this->maxRunTime); - } } diff --git a/webapp/src/Entity/Judging.php b/webapp/src/Entity/Judging.php index 2526b48c6a..da14eff906 100644 --- a/webapp/src/Entity/Judging.php +++ b/webapp/src/Entity/Judging.php @@ -56,6 +56,16 @@ class Judging extends BaseApiEntity #[Serializer\Exclude] private string|float|null $endtime = null; + #[ORM\Column( + type: 'decimal', + precision: 32, + scale: 9, + nullable: true, + options: ['comment' => 'The maximum runtime for all runs that resulted in the verdict', 'unsigned' => true] + )] + #[Serializer\Exclude] + private string|float|null $maxRuntimeForVerdict = null; + #[ORM\Column( length: 32, nullable: true, @@ -250,6 +260,26 @@ public function getRelativeEndTime(): ?string return $this->getEndtime() ? Utils::relTime($this->getEndtime() - $this->getContest()->getStarttime()) : null; } + public function setMaxRuntimeForVerdict(string|float $maxRuntimeForVerdict): Judging + { + $this->maxRuntimeForVerdict = $maxRuntimeForVerdict; + return $this; + } + + public function getMaxRuntimeForVerdict(): string|float|null + { + return $this->maxRuntimeForVerdict; + } + + #[Serializer\VirtualProperty] + #[Serializer\SerializedName('max_run_time')] + #[Serializer\Type('float')] + #[OA\Property(nullable: true)] + public function getRoundedMaxRuntimeForVerdict(): ?float + { + return $this->maxRuntimeForVerdict ? Utils::roundedFloat((float)$this->maxRuntimeForVerdict) : null; + } + public function setResult(?string $result): Judging { $this->result = $result;