From 0f9a160553329d41da8d9c93183b86c48b235a07 Mon Sep 17 00:00:00 2001 From: DOMjudge team Date: Wed, 18 Sep 2024 12:33:55 +0500 Subject: [PATCH] Check that last seen event ID exists while emitting the event feed This will throw an error, for example, when the database gets reloaded while there is a running process serving the event feed to a connected client. We encountered this problem at WF Astana. --- .../src/Controller/API/ContestController.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/webapp/src/Controller/API/ContestController.php b/webapp/src/Controller/API/ContestController.php index bea5588e4d..8cd0112798 100644 --- a/webapp/src/Controller/API/ContestController.php +++ b/webapp/src/Controller/API/ContestController.php @@ -39,6 +39,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -654,6 +655,7 @@ public function getEventFeedAction( $response->setCallback(function () use ($format, $cid, $contest, $request, $since_id, $types, $strict, $stream, $metadataFactory, $kernel) { $lastUpdate = 0; $lastIdSent = max(0, $since_id); // Don't try to look for event_id=0 + $lastIdExists = $since_id === -1; $typeFilter = false; if ($types) { $typeFilter = explode(',', $types); @@ -721,12 +723,15 @@ public function getEventFeedAction( // Add missing state events that should have happened already. $this->eventLogService->addMissingStateEvents($contest); - // We fetch *all* events after the last seen to check that + // We fetch *all* events from the last seen to check that // we don't skip events that are committed out of order. + // This includes the last seen event itself, just to check + // that the database is consistent and, for example, has + // not been reloaded while this process is (long) running. $q = $this->em->createQueryBuilder() ->from(Event::class, 'e') ->select('e') - ->andWhere('e.eventid > :lastIdSent') + ->andWhere('e.eventid >= :lastIdSent') ->setParameter('lastIdSent', $lastIdSent) ->orderBy('e.eventid', 'ASC') ->getQuery(); @@ -734,6 +739,15 @@ public function getEventFeedAction( /** @var Event[] $events */ $events = $q->getResult(); + if ($lastIdExists) { + if (count($events) == 0 || $events[0]->getEventid() !== $lastIdSent) { + throw new HttpException(500, sprintf('Cannot find event %d in database anymore', $lastIdSent)); + } + // Remove the previously last sent event. We just fetched + // it to make sure it's there. + unset($events[0]); + } + // Look for any missing sequential events and wait for them to // be committed if so. $missingEvents = false; @@ -860,6 +874,7 @@ public function getEventFeedAction( flush(); $lastUpdate = Utils::now(); $lastIdSent = $event->getEventid(); + $lastIdExists = true; $numEventsSent++; if ($missingEvents && $event->getEventid() >= $lastFoundId) {