- Add StreamRecoveryManager for handling stream timeouts - Monitor stream activity with 45-second timeout - Automatic recovery with 2 retry attempts - Proper cleanup on stream completion Fixes #1964 Co-authored-by: Keoma Wright <founder@lovemedia.org.za>
93 lines
2.0 KiB
TypeScript
93 lines
2.0 KiB
TypeScript
import { createScopedLogger } from '~/utils/logger';
|
|
|
|
const logger = createScopedLogger('stream-recovery');
|
|
|
|
export interface StreamRecoveryOptions {
|
|
maxRetries?: number;
|
|
timeout?: number;
|
|
onTimeout?: () => void;
|
|
onRecovery?: () => void;
|
|
}
|
|
|
|
export class StreamRecoveryManager {
|
|
private _retryCount = 0;
|
|
private _timeoutHandle: NodeJS.Timeout | null = null;
|
|
private _lastActivity: number = Date.now();
|
|
private _isActive = true;
|
|
|
|
constructor(private _options: StreamRecoveryOptions = {}) {
|
|
this._options = {
|
|
maxRetries: 3,
|
|
timeout: 30000, // 30 seconds default
|
|
..._options,
|
|
};
|
|
}
|
|
|
|
startMonitoring() {
|
|
this._resetTimeout();
|
|
}
|
|
|
|
updateActivity() {
|
|
this._lastActivity = Date.now();
|
|
this._resetTimeout();
|
|
}
|
|
|
|
private _resetTimeout() {
|
|
if (this._timeoutHandle) {
|
|
clearTimeout(this._timeoutHandle);
|
|
}
|
|
|
|
if (!this._isActive) {
|
|
return;
|
|
}
|
|
|
|
this._timeoutHandle = setTimeout(() => {
|
|
if (this._isActive) {
|
|
logger.warn('Stream timeout detected');
|
|
this._handleTimeout();
|
|
}
|
|
}, this._options.timeout);
|
|
}
|
|
|
|
private _handleTimeout() {
|
|
if (this._retryCount >= (this._options.maxRetries || 3)) {
|
|
logger.error('Max retries reached for stream recovery');
|
|
this.stop();
|
|
|
|
return;
|
|
}
|
|
|
|
this._retryCount++;
|
|
logger.info(`Attempting stream recovery (attempt ${this._retryCount})`);
|
|
|
|
if (this._options.onTimeout) {
|
|
this._options.onTimeout();
|
|
}
|
|
|
|
// Reset monitoring after recovery attempt
|
|
this._resetTimeout();
|
|
|
|
if (this._options.onRecovery) {
|
|
this._options.onRecovery();
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
this._isActive = false;
|
|
|
|
if (this._timeoutHandle) {
|
|
clearTimeout(this._timeoutHandle);
|
|
this._timeoutHandle = null;
|
|
}
|
|
}
|
|
|
|
getStatus() {
|
|
return {
|
|
isActive: this._isActive,
|
|
retryCount: this._retryCount,
|
|
lastActivity: this._lastActivity,
|
|
timeSinceLastActivity: Date.now() - this._lastActivity,
|
|
};
|
|
}
|
|
}
|