backend: replace recording cleanup timer with task scheduler registration

This commit is contained in:
Carlos Santos 2025-03-31 13:14:30 +02:00
parent 4060d1682e
commit 10181c69ee
2 changed files with 13 additions and 49 deletions

View File

@ -23,6 +23,7 @@ import { RecordingHelper } from '../helpers/recording.helper.js';
import { import {
MEET_RECORDING_LOCK_GC_INTERVAL, MEET_RECORDING_LOCK_GC_INTERVAL,
MEET_RECORDING_LOCK_TTL, MEET_RECORDING_LOCK_TTL,
MEET_RECORDING_STARTED_TIMEOUT,
MEET_S3_BUCKET, MEET_S3_BUCKET,
MEET_S3_RECORDINGS_PREFIX, MEET_S3_RECORDINGS_PREFIX,
MEET_S3_SUBBUCKET MEET_S3_SUBBUCKET
@ -80,10 +81,12 @@ export class RecordingService {
const { recordingId } = recordingInfo; const { recordingId } = recordingInfo;
const recordingPromise = new Promise<MeetRecordingInfo>((resolve, reject) => { const recordingPromise = new Promise<MeetRecordingInfo>((resolve, reject) => {
this.taskSchedulerService.scheduleRecordingCleanupTimer( this.taskSchedulerService.registerTask({
roomId, name: `${roomId}_recording_timeout`,
this.handleRecordingLockTimeout.bind(this, recordingId, roomId, reject) type: 'timeout',
); scheduleOrDelay: MEET_RECORDING_STARTED_TIMEOUT,
callback: this.handleRecordingLockTimeout.bind(this, recordingId, roomId, reject)
});
this.systemEventService.once(SystemEventType.RECORDING_ACTIVE, (payload: Record<string, unknown>) => { this.systemEventService.once(SystemEventType.RECORDING_ACTIVE, (payload: Record<string, unknown>) => {
// This listener is triggered only for the instance that started the recording. // This listener is triggered only for the instance that started the recording.
@ -92,7 +95,7 @@ export class RecordingService {
payload?.recordingId === recordingId && payload?.roomId === roomId; payload?.recordingId === recordingId && payload?.roomId === roomId;
if (isEventForCurrentRecording) { if (isEventForCurrentRecording) {
this.taskSchedulerService.cancelRecordingCleanupTimer(roomId); this.taskSchedulerService.cancelTask(`${roomId}_recording_timeout`);
resolve(recordingInfo); resolve(recordingInfo);
} else { } else {
this.logger.error('Received recording active event with mismatched recording ID:', payload); this.logger.error('Received recording active event with mismatched recording ID:', payload);
@ -121,7 +124,7 @@ export class RecordingService {
} }
// Cancel the recording cleanup timer if it is running // Cancel the recording cleanup timer if it is running
this.taskSchedulerService.cancelRecordingCleanupTimer(roomId); this.taskSchedulerService.cancelTask(`${roomId}_recording_timeout`);
// Remove the listener for the EGRESS_STARTED event. // Remove the listener for the EGRESS_STARTED event.
this.systemEventService.off(SystemEventType.RECORDING_ACTIVE); this.systemEventService.off(SystemEventType.RECORDING_ACTIVE);
@ -587,7 +590,9 @@ export class RecordingService {
const lockAge = Date.now() - (createdAt || Date.now()); const lockAge = Date.now() - (createdAt || Date.now());
if (lockAge < LOCK_GRACE_PERIOD) { if (lockAge < LOCK_GRACE_PERIOD) {
this.logger.debug(`Lock for room ${roomId} is too recent (${ms(lockAge)}), skipping orphan lock cleanup`); this.logger.debug(
`Lock for room ${roomId} is too recent (${ms(lockAge)}), skipping orphan lock cleanup`
);
return; return;
} }

View File

@ -6,7 +6,6 @@ import { MutexService } from './mutex.service.js';
import { MeetLock } from '../helpers/redis.helper.js'; import { MeetLock } from '../helpers/redis.helper.js';
import ms from 'ms'; import ms from 'ms';
import { CronExpressionParser } from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import { MEET_RECORDING_STARTED_TIMEOUT } from '../environment.js';
export type TaskType = 'cron' | 'timeout'; export type TaskType = 'cron' | 'timeout';
@ -78,47 +77,6 @@ export class TaskSchedulerService {
this.roomGarbageCollectorJob.start(); this.roomGarbageCollectorJob.start();
} }
/**
* Schedules a cleanup timer for a recording that has just started.
*
* If the egress_started webhook is not received before the timer expires,
* this timer will execute a cleanup callback by stopping the recording and releasing
* the active lock for the specified room.
*/
async scheduleRecordingCleanupTimer(roomId: string, cleanupCallback: () => Promise<void>): Promise<void> {
this.logger.debug(`Recording cleanup timer (${MEET_RECORDING_STARTED_TIMEOUT}) scheduled for room ${roomId}.`);
// Schedule a timeout to run the cleanup callback after a specified time
const timeoutMs = ms(MEET_RECORDING_STARTED_TIMEOUT);
const timer = setTimeout(async () => {
this.logger.warn(`Recording cleanup timer expired for room ${roomId}. Initiating cleanup process.`);
this.recordingCleanupTimers.delete(roomId);
await cleanupCallback();
}, timeoutMs);
this.recordingCleanupTimers.set(roomId, timer);
}
cancelRecordingCleanupTimer(roomId: string): void {
const timer = this.recordingCleanupTimers.get(roomId);
if (timer) {
clearTimeout(timer);
this.recordingCleanupTimers.delete(roomId);
this.logger.info(`Recording cleanup timer cancelled for room ${roomId}`);
}
}
async startRecordingLockGarbageCollector(callbackFn: () => Promise<void>): Promise<void> {
// Create a cron job to run every minute
const recordingLockGarbageCollectorJob = new CronJob('0 * * * * *', async () => {
try {
await callbackFn();
} catch (error) {
this.logger.error('Error running recording lock garbage collection:', error);
}
});
}
/** /**
* Registers a new task to be scheduled. * Registers a new task to be scheduled.
* If the task is already registered, it will not be added again. * If the task is already registered, it will not be added again.
@ -176,6 +134,7 @@ export class TaskSchedulerService {
const timeoutId = setTimeout( const timeoutId = setTimeout(
async () => { async () => {
try { try {
this.scheduledTasks.delete(name);
await callback(); await callback();
} catch (error) { } catch (error) {
this.logger.error(`Error running timeout task "${name}":`, error); this.logger.error(`Error running timeout task "${name}":`, error);