Usuwanie nagrania i webinaru zatrzymuje zadanie u dostawcy
Deploy PROD / deploy (push) Successful in 19s
Deploy PROD / deploy (push) Successful in 19s
Ekran Zasoby pozwalal POSPRZATAC osierocone nagrania, ale nie zapobiegal ich powstawaniu. Dwie sciezki tworzyly je dalej: - `RecordingService.delete` kasowal wpis, zostawiajac zadanie dzialajace u dostawcy w nieskonczonosc, - `WebinarService.delete` kasowal nagrania KASKADOWO, przez co trwajace zadanie stawalo sie niewidoczne dla aplikacji: scheduler domykajacy nagrania szuka po naszych wpisach, a tych juz nie bylo. Ta druga sciezka wywolala realny incydent: webinar usunieto w trakcie nagrywania, a przegladarka po stronie Egress krecila sie kilkanascie godzin i zjadala jedna piata procesora na serwerze dzielonym z dwiema innymi aplikacjami. Zaden ekran tego nie pokazywal. Obie zatrzymuja teraz zadanie PRZED skasowaniem wpisu. BLAD ZATRZYMANIA NIE BLOKUJE USUNIECIA Najczestsza przyczyna to zadanie, ktore samo sie juz zakonczylo, a administrator ma prawo posprzatac wpis niezaleznie od tego, co odpowie dostawca. Slad idzie do logu, a zadanie mimo wszystko dzialajace pokaze ekran Zasoby jako osierocone, razem z przyciskiem zatrzymania. Zabezpieczenie dziala wiec z dwoch stron: zapobiega i naprawia. Sprawdzone, ze nie powstaje cykl zaleznosci: `LiveKitEgressService` zalezy wylacznie od repozytoriow, ustawien i fabryki dostawcow, wiec nie siega z powrotem ani do `RecordingService`, ani do `WebinarService`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ public class RecordingService {
|
||||
private final FileStorageService storage;
|
||||
private final SettingsService settings;
|
||||
private final VideoService videoService;
|
||||
private final com.softspm.webinar.video.LiveKitEgressService egressService;
|
||||
|
||||
/**
|
||||
* Referencja na samego siebie, przez proxy Springa.
|
||||
@@ -80,12 +81,14 @@ public class RecordingService {
|
||||
FileStorageService storage,
|
||||
SettingsService settings,
|
||||
VideoService videoService,
|
||||
com.softspm.webinar.video.LiveKitEgressService egressService,
|
||||
@org.springframework.context.annotation.Lazy RecordingService self) {
|
||||
this.recordingRepository = recordingRepository;
|
||||
this.webinarRepository = webinarRepository;
|
||||
this.storage = storage;
|
||||
this.settings = settings;
|
||||
this.videoService = videoService;
|
||||
this.egressService = egressService;
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
@@ -365,6 +368,34 @@ public class RecordingService {
|
||||
public void delete(Long recordingId) {
|
||||
Recording recording = require(recordingId);
|
||||
String objectKey = recording.getObjectKey();
|
||||
|
||||
/*
|
||||
* Nagranie WCIAZ TRWAJACE trzeba najpierw zatrzymac u dostawcy.
|
||||
*
|
||||
* Skasowanie samego wpisu zostawialo zadanie dzialajace w nieskonczonosc:
|
||||
* Egress nagrywa dalej, a scheduler domykajacy nagrania szuka po naszych
|
||||
* wpisach, wiec czegos, czego w bazie juz nie ma, nigdy nie zobaczy.
|
||||
* Zdarzylo sie to realnie: przegladarka po stronie Egress krecila sie
|
||||
* kilkanascie godzin i zjadala jedna piata procesora, a zaden ekran
|
||||
* w aplikacji tego nie pokazywal.
|
||||
*
|
||||
* Blad zatrzymania NIE BLOKUJE usuniecia: najczestsza przyczyna to
|
||||
* zadanie, ktore samo sie juz zakonczylo, a administrator ma prawo
|
||||
* posprzatac wpis niezaleznie od tego, co odpowie dostawca. Slad idzie
|
||||
* do logu, a nagranie wciaz widoczne u dostawcy zobaczy ekran Zasoby
|
||||
* jako osierocone.
|
||||
*/
|
||||
if (recording.getStatus() == RecordingStatus.DOWNLOADING
|
||||
&& recording.getProviderRecordingId() != null
|
||||
&& !recording.getProviderRecordingId().isBlank()) {
|
||||
try {
|
||||
egressService.stopRecording(recording.getProviderRecordingId());
|
||||
} catch (Exception e) {
|
||||
log.warn("Nie udalo sie zatrzymac nagrania {} przy usuwaniu: {}",
|
||||
recording.getProviderRecordingId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
recordingRepository.delete(recording);
|
||||
|
||||
if (objectKey != null) {
|
||||
|
||||
@@ -31,7 +31,12 @@ public class WebinarService {
|
||||
static final Set<RegistrationStatus> ACTIVE_STATUSES =
|
||||
Set.of(RegistrationStatus.PENDING, RegistrationStatus.CONFIRMED);
|
||||
|
||||
private static final org.slf4j.Logger log =
|
||||
org.slf4j.LoggerFactory.getLogger(WebinarService.class);
|
||||
|
||||
private final com.softspm.webinar.video.VideoProviderFactory providerFactory;
|
||||
private final com.softspm.webinar.repository.RecordingRepository recordingRepository;
|
||||
private final com.softspm.webinar.video.LiveKitEgressService egressService;
|
||||
private final WebinarRepository webinarRepository;
|
||||
private final RegistrationRepository registrationRepository;
|
||||
private final InteractionRepository interactionRepository;
|
||||
@@ -47,7 +52,9 @@ public class WebinarService {
|
||||
SlugGenerator slugGenerator,
|
||||
SettingsService settings,
|
||||
ReminderRuleService reminderRuleService,
|
||||
HtmlSanitizer htmlSanitizer) {
|
||||
HtmlSanitizer htmlSanitizer,
|
||||
com.softspm.webinar.repository.RecordingRepository recordingRepository,
|
||||
com.softspm.webinar.video.LiveKitEgressService egressService) {
|
||||
this.providerFactory = providerFactory;
|
||||
this.webinarRepository = webinarRepository;
|
||||
this.registrationRepository = registrationRepository;
|
||||
@@ -55,6 +62,8 @@ public class WebinarService {
|
||||
this.slugGenerator = slugGenerator;
|
||||
this.settings = settings;
|
||||
this.reminderRuleService = reminderRuleService;
|
||||
this.recordingRepository = recordingRepository;
|
||||
this.egressService = egressService;
|
||||
this.htmlSanitizer = htmlSanitizer;
|
||||
}
|
||||
|
||||
@@ -117,6 +126,37 @@ public class WebinarService {
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
Webinar webinar = require(id);
|
||||
|
||||
/*
|
||||
* Nagrania WCIAZ TRWAJACE trzeba zatrzymac u dostawcy PRZED kasowaniem.
|
||||
*
|
||||
* Wpisy znikaja tu kaskadowo, wiec zadanie nagrywajace zostawaloby
|
||||
* dzialajace i **niewidoczne dla aplikacji**: scheduler domykajacy
|
||||
* nagrania szuka po naszych wpisach, a tych juz nie ma. Zdarzylo sie
|
||||
* to realnie: webinar usunieto w trakcie nagrywania, a przegladarka
|
||||
* po stronie Egress krecila sie kilkanascie godzin, zjadajac jedna
|
||||
* piata procesora serwera dzielonego z innymi aplikacjami.
|
||||
*
|
||||
* Blad zatrzymania nie blokuje usuniecia: administrator ma prawo
|
||||
* skasowac webinar niezaleznie od tego, co odpowie dostawca. Slad idzie
|
||||
* do logu, a zadanie wciaz dzialajace pokaze ekran Zasoby jako
|
||||
* osierocone, razem z przyciskiem zatrzymania.
|
||||
*/
|
||||
for (var recording : recordingRepository.findByWebinarIdAndStatusOrderByCreatedAtDesc(
|
||||
webinar.getId(), com.softspm.webinar.domain.RecordingStatus.DOWNLOADING)) {
|
||||
String egressId = recording.getProviderRecordingId();
|
||||
if (egressId == null || egressId.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
egressService.stopRecording(egressId);
|
||||
log.info("Zatrzymano nagranie {} przed usunieciem webinaru {}", egressId, id);
|
||||
} catch (Exception e) {
|
||||
log.warn("Nie udalo sie zatrzymac nagrania {} przed usunieciem webinaru {}: {}",
|
||||
egressId, id, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
webinarRepository.delete(webinar);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user