From 0292b03d5e14b731abe5a045b95e538f010cb3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Zatorski?= Date: Sat, 5 Sep 2026 08:19:12 +0200 Subject: [PATCH] Zadanie ubite razem z kontenerem znika z ekranu Zasoby po osmiu godzinach Egress zapisuje stan zadan w Redisie, a status koncowy ustawia sam proces obslugujacy nagranie. Gdy proces zostanie ubity razem z kontenerem (restart, wyczerpanie pamieci), status nigdy sie nie zmienia: zadanie figuruje jako EGRESS_ACTIVE **na zawsze**. `StopEgress` konczy sie wtedy sukcesem i NIC NIE ZMIENIA, bo nie ma juz czego zatrzymywac. Sprawdzone na zywo: wpis wisial po restarcie Egress, panel pokazywal go jako osierocone zadanie, a klikniecie "Zatrzymaj" logowalo "Zatrzymano Egress" i zostawialo wszystko po staremu. Zuzycie procesora bylo przy tym zerowe, czyli zadanie faktycznie nie istnialo. Ekran mowil wiec nieprawde i nie dawalo sie tego odkrecic z poziomu aplikacji: wpis usunieto recznie z Redisa. Liczy sie teraz takze WIEK zadania, nie tylko jego status. Osiem godzin: dluzej niz najdluzszy sensowny webinar razem z montazem, a krocej niz doba, zeby wpis po nocnym restarcie nie straszyl rano. `startedAt` jest w nanosekundach od epoki. Proto podaje sam `int64` bez jednostki, ale webhooki LiveKit niosa tam wartosci 19-cyfrowe, a `duration` w `FileInfo` jest liczone tak samo. Zero oznacza zadanie, ktore nie zdazylo wystartowac: traktujemy je jak biezace, zeby swiezo zlecone nagranie nie znikalo z ekranu, zanim ruszy. Co-Authored-By: Claude Opus 5 --- .../webinar/video/LiveKitEgressService.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/backend/src/main/java/com/softspm/webinar/video/LiveKitEgressService.java b/backend/src/main/java/com/softspm/webinar/video/LiveKitEgressService.java index 4b611dc..bd4deb1 100644 --- a/backend/src/main/java/com/softspm/webinar/video/LiveKitEgressService.java +++ b/backend/src/main/java/com/softspm/webinar/video/LiveKitEgressService.java @@ -54,6 +54,15 @@ public class LiveKitEgressService { private static final SecureRandom RANDOM = new SecureRandom(); + /** + * Po jakim czasie zadanie u dostawcy uznajemy za martwe, niezaleznie od + * jego statusu. + * + * Osiem godzin: dluzej niz najdluzszy sensowny webinar razem z montazem, + * a krocej niz doba, zeby wpis po nocnym restarcie nie straszyl rano. + */ + private static final java.time.Duration STALE_EGRESS_AFTER = java.time.Duration.ofHours(8); + private final SettingsService settings; private final WebinarRepository webinarRepository; private final RecordingRepository recordingRepository; @@ -350,11 +359,43 @@ public class LiveKitEgressService { if (!response.isSuccessful() || response.body() == null) { throw new ValidationException("Serwer LiveKit odpowiedzial: " + describe(response.code())); } + /* + * ZADANIE UBITE ZOSTAJE W REJESTRZE JAKO AKTYWNE + * + * Egress zapisuje stan w Redisie, a status koncowy ustawia sam + * proces obslugujacy nagranie. Gdy proces zostanie ubity razem + * z kontenerem (restart, wyczerpanie pamieci), status nigdy sie + * nie zmienia: zadanie figuruje jako EGRESS_ACTIVE na zawsze. + * `StopEgress` konczy sie wtedy sukcesem i NIC NIE ZMIENIA, bo + * nie ma juz czego zatrzymywac. + * + * Sprawdzone na zywo: wpis wisial po restarcie Egress, a panel + * pokazywal go jako osierocone zadanie mimo zerowego zuzycia + * procesora i mimo udanego zatrzymania. + * + * Dlatego liczy sie takze WIEK. Zadanie starsze niz okno sensownego + * nagrania jest martwe niezaleznie od tego, co twierdzi status: + * najdluzszy webinar trwa kilka godzin, a montaz pliku minuty. + */ + long staleBefore = System.currentTimeMillis() - STALE_EGRESS_AFTER.toMillis(); + return response.body().stream() .filter(info -> switch (info.getStatus()) { case EGRESS_STARTING, EGRESS_ACTIVE, EGRESS_ENDING -> true; default -> false; }) + /* + * `startedAt` jest w NANOSEKUNDACH od epoki: proto podaje + * sam `int64` bez jednostki, ale webhooki LiveKit niosa + * tam wartosci 19-cyfrowe, a `duration` w `FileInfo` jest + * liczone tak samo. Stad dzielenie przez milion. + * + * Zero oznacza zadanie, ktore nie zdazylo wystartowac: + * traktujemy je jak biezace, zeby swiezo zlecone nagranie + * nie znikalo z ekranu, zanim ruszy. + */ + .filter(info -> info.getStartedAt() == 0L + || info.getStartedAt() / 1_000_000L > staleBefore) .map(LivekitEgress.EgressInfo::getEgressId) .toList(); } catch (ValidationException e) {