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 8eb6239515..fd0c0fa6a2 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1023,9 +1023,26 @@ 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. - $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); - $sendJudgingEvent = true; } $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;