@casoon/auditmysite
Version:
Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe
830 lines β’ 32.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventDrivenQueue = void 0;
const events_1 = require("events");
const backpressure_controller_1 = require("../backpressure-controller");
const resource_monitor_1 = require("../resource-monitor");
const queue_state_1 = require("../../types/queue-state");
const file_queue_state_adapter_1 = require("../queue/file-queue-state-adapter");
/**
* @deprecated EventDrivenQueue class is deprecated and will be removed in v3.0.0
*
* Use PageAnalysisEmitter for unified event handling instead.
* This class is maintained for backward compatibility only.
*
* MIGRATION PATH:
* ```typescript
* // OLD (deprecated)
* const queue = new EventDrivenQueue({ eventCallbacks: { ... } });
*
* // NEW (recommended)
* const emitter = new PageAnalysisEmitter({ callbacks: { ... } });
* // OR via AccessibilityChecker
* const checker = new AccessibilityChecker({ enableUnifiedEvents: true });
* checker.setUnifiedEventCallbacks({ ... });
* ```
*/
class EventDrivenQueue extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.queue = [];
this.completed = [];
this.failed = [];
this.activeWorkers = new Set();
this.isProcessing = false;
this.startTime = null;
this.lastProgressUpdate = 0;
this.progressUpdateInterval = 1000;
this.statusInterval = null;
this.hardTimeoutTracker = new Map();
this.globalTimeoutTimer = null;
this.autoSaveTimer = null;
this.stateId = '';
this.currentState = null;
this.options = {
maxRetries: 3,
maxConcurrent: 1,
priorityPatterns: [
{ pattern: '/home', priority: 10 },
{ pattern: '/', priority: 9 },
{ pattern: '/about', priority: 8 },
{ pattern: '/contact', priority: 7 }
],
retryDelay: 1000,
enableEvents: true,
enableShortStatus: true,
statusUpdateInterval: 5000, // Reduced frequency - every 5 seconds instead of 2
enableBackpressure: false,
enableResourceMonitoring: false,
hardTimeout: 30000,
globalTimeout: 300000,
enablePersistence: false,
autoSave: true,
autoSaveInterval: 10000,
resumable: true,
...options
};
this.setupBackpressure();
this.setupResourceMonitoring();
this.setupPersistence();
if (this.options.enableEvents) {
this.setupEventListeners();
}
}
/**
* Setup backpressure controller
*/
setupBackpressure() {
if (!this.options.enableBackpressure)
return;
const backpressureConfig = {
enabled: true,
maxQueueSize: 500,
maxMemoryUsageMB: 2048,
maxCpuUsagePercent: 80,
minDelayMs: 50,
maxDelayMs: 5000,
activationThreshold: 0.8,
deactivationThreshold: 0.6,
...this.options.backpressureConfig
};
this.backpressureController = new backpressure_controller_1.AdaptiveBackpressureController(backpressureConfig);
this.backpressureController.on('backpressureActivated', (data) => {
this.options.eventCallbacks?.onBackpressureActivated?.('Memory/CPU pressure detected');
});
this.backpressureController.on('backpressureDeactivated', (data) => {
this.options.eventCallbacks?.onBackpressureDeactivated?.();
});
this.backpressureController.on('memoryWarning', (data) => {
this.options.eventCallbacks?.onResourceWarning?.(data.current, data.threshold);
});
this.backpressureController.on('memoryCritical', (data) => {
this.options.eventCallbacks?.onResourceCritical?.(data.current, data.max);
});
this.backpressureController.on('gcTriggered', (data) => {
this.options.eventCallbacks?.onGarbageCollection?.(data.beforeMB);
});
}
/**
* Setup persistence functionality
*/
setupPersistence() {
if (!this.options.enablePersistence)
return;
// Initialize state adapter
this.stateAdapter = this.options.stateAdapter || new file_queue_state_adapter_1.FileQueueStateAdapter();
// Generate unique state ID if not provided
this.stateId = this.options.stateId || this.generateStateId();
// Setup auto-save if enabled
if (this.options.autoSave && this.options.autoSaveInterval) {
this.startAutoSave();
}
}
/**
* Generate unique state ID based on timestamp and random string
*/
generateStateId() {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
return `queue-${timestamp}-${random}`;
}
/**
* Setup resource monitoring
*/
setupResourceMonitoring() {
if (!this.options.enableResourceMonitoring)
return;
const resourceConfig = {
enabled: true,
samplingIntervalMs: 2000,
memoryWarningThresholdMB: 1536,
memoryCriticalThresholdMB: 2048,
...this.options.resourceMonitorConfig
};
this.resourceMonitor = new resource_monitor_1.ResourceMonitor(resourceConfig);
this.resourceMonitor.on('resourceAlert', (alert) => {
if (alert.level === 'warning') {
this.options.eventCallbacks?.onResourceWarning?.(alert.current, alert.threshold);
}
else {
this.options.eventCallbacks?.onResourceCritical?.(alert.current, alert.threshold);
}
});
this.resourceMonitor.on('criticalAlert', (alert) => {
// Force garbage collection on critical memory alerts
if (alert.metric === 'rssMemory' || alert.metric === 'heapUsage') {
const gcSuccess = this.resourceMonitor?.forceGC();
if (gcSuccess) {
this.options.eventCallbacks?.onGarbageCollection?.(alert.current);
}
}
});
}
setupEventListeners() {
// Interne Event-Handler
this.on('url-added', (url, priority) => {
this.emit('queue:urlAdded', { url, priority, timestamp: new Date() });
this.options.eventCallbacks?.onUrlAdded?.(url, priority);
});
this.on('url-started', (url) => {
this.emit('queue:urlStarted', { url, timestamp: new Date() });
this.options.eventCallbacks?.onUrlStarted?.(url);
});
this.on('url-completed', (url, result, duration) => {
this.emit('queue:urlCompleted', { url, result, duration, timestamp: new Date() });
this.options.eventCallbacks?.onUrlCompleted?.(url, result, duration);
});
this.on('url-failed', (url, error, attempts) => {
this.emit('queue:urlFailed', { url, error, attempts, timestamp: new Date() });
this.options.eventCallbacks?.onUrlFailed?.(url, error, attempts);
});
this.on('url-retrying', (url, attempts) => {
this.emit('queue:urlRetrying', { url, attempts, timestamp: new Date() });
this.options.eventCallbacks?.onUrlRetrying?.(url, attempts);
});
this.on('queue-empty', () => {
this.emit('queue:empty', { timestamp: new Date() });
this.options.eventCallbacks?.onQueueEmpty?.();
});
this.on('progress-update', (stats) => {
this.emit('queue:progressUpdate', { stats, timestamp: new Date() });
this.options.eventCallbacks?.onProgressUpdate?.(stats);
});
this.on('error', (error) => {
this.emit('queue:error', { error, timestamp: new Date() });
this.options.eventCallbacks?.onError?.(error);
});
}
addUrls(urls) {
const newUrls = urls.filter(url => !this.queue.some(q => q.url === url));
newUrls.forEach(url => {
const priority = this.calculatePriority(url);
const queuedUrl = {
url,
priority,
status: 'pending',
attempts: 0
};
this.queue.push(queuedUrl);
this.emit('url-added', url, priority);
});
// Sort by priority (highest first)
this.queue.sort((a, b) => b.priority - a.priority);
this.updateProgress();
}
async getNextUrl() {
// Check backpressure delay
const delay = this.backpressureController?.getCurrentDelay() || 0;
if (delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
const pendingUrl = this.queue.find(q => q.status === 'pending');
if (pendingUrl && this.activeWorkers.size < this.options.maxConcurrent) {
pendingUrl.status = 'in-progress';
pendingUrl.startedAt = new Date();
pendingUrl.attempts++;
this.activeWorkers.add(pendingUrl.url);
// Set hard timeout for this item
if (this.options.hardTimeout) {
const timeoutId = setTimeout(() => {
this.markFailed(pendingUrl.url, `Hard timeout after ${this.options.hardTimeout}ms`);
}, this.options.hardTimeout);
this.hardTimeoutTracker.set(pendingUrl.url, timeoutId);
}
this.emit('url-started', pendingUrl.url);
this.updateProgress();
this.updateBackpressure();
return pendingUrl;
}
return null;
}
markCompleted(url, result) {
const queuedUrl = this.queue.find(q => q.url === url);
if (queuedUrl) {
queuedUrl.status = 'completed';
queuedUrl.result = result;
queuedUrl.completedAt = new Date();
queuedUrl.duration = queuedUrl.completedAt.getTime() - queuedUrl.startedAt.getTime();
this.completed.push(queuedUrl);
this.queue = this.queue.filter(q => q.url !== url);
this.activeWorkers.delete(url);
// Clear hard timeout
const timeoutId = this.hardTimeoutTracker.get(url);
if (timeoutId) {
clearTimeout(timeoutId);
this.hardTimeoutTracker.delete(url);
}
this.emit('url-completed', url, result, queuedUrl.duration);
this.updateProgress();
this.updateBackpressure(false); // Success, no error
this.checkQueueEmpty();
}
}
markFailed(url, error) {
const queuedUrl = this.queue.find(q => q.url === url);
if (queuedUrl) {
// Clear hard timeout
const timeoutId = this.hardTimeoutTracker.get(url);
if (timeoutId) {
clearTimeout(timeoutId);
this.hardTimeoutTracker.delete(url);
}
if (queuedUrl.attempts < this.options.maxRetries) {
// Retry logic with backpressure consideration
queuedUrl.status = 'retrying';
this.emit('url-retrying', url, queuedUrl.attempts);
// Calculate retry delay with backpressure
const baseDelay = this.options.retryDelay || 1000;
const backpressureDelay = this.backpressureController?.getCurrentDelay() || 0;
const totalDelay = baseDelay + backpressureDelay;
// Schedule retry
setTimeout(() => {
if (this.queue.find(q => q.url === url)) { // Still in queue
queuedUrl.status = 'pending';
this.updateProgress();
}
}, totalDelay);
}
else {
// Max retries reached
queuedUrl.status = 'failed';
queuedUrl.error = error;
queuedUrl.completedAt = new Date();
queuedUrl.duration = queuedUrl.completedAt.getTime() - queuedUrl.startedAt.getTime();
this.failed.push(queuedUrl);
this.queue = this.queue.filter(q => q.url !== url);
this.activeWorkers.delete(url);
this.emit('url-failed', url, error, queuedUrl.attempts);
this.updateProgress();
this.updateBackpressure(true); // Error occurred
this.checkQueueEmpty();
}
}
}
/**
* Update backpressure controller with current state
*/
updateBackpressure(hasError = false) {
if (!this.backpressureController)
return;
this.backpressureController.updateQueueState(this.queue.length, this.activeWorkers.size, hasError);
}
checkQueueEmpty() {
const pendingUrls = this.queue.filter(q => q.status === 'pending' || q.status === 'retrying');
console.log(`π checkQueueEmpty: pending=${pendingUrls.length}, activeWorkers=${this.activeWorkers.size}, totalQueue=${this.queue.length}`);
if (pendingUrls.length === 0 && this.activeWorkers.size === 0) {
console.log('π Queue is empty - processing complete');
this.isProcessing = false;
// Stop monitoring
this.resourceMonitor?.stop();
// Clear any remaining timeouts
for (const timeoutId of this.hardTimeoutTracker.values()) {
clearTimeout(timeoutId);
}
this.hardTimeoutTracker.clear();
if (this.globalTimeoutTimer) {
clearTimeout(this.globalTimeoutTimer);
this.globalTimeoutTimer = null;
}
this.emit('queue-empty');
}
}
updateProgress() {
const now = Date.now();
if (now - this.lastProgressUpdate < this.progressUpdateInterval) {
return;
}
this.lastProgressUpdate = now;
const stats = this.getStats();
this.emit('progress-update', stats);
}
getStats() {
// Get unique URLs from queue, completed and failed to avoid double-counting
const allUrls = new Set([...this.queue.map(q => q.url), ...this.completed.map(q => q.url), ...this.failed.map(q => q.url)]);
const total = allUrls.size;
const pending = this.queue.filter(q => q.status === 'pending').length;
const inProgress = this.queue.filter(q => q.status === 'in-progress').length;
const completed = this.completed.length;
const failed = this.failed.length;
const retrying = this.queue.filter(q => q.status === 'retrying').length;
const progress = total > 0 ? ((completed + failed) / total) * 100 : 0;
// Debug progress calculation
console.log(`π Queue stats: total=${total}, completed=${completed}, failed=${failed}, progress=${progress.toFixed(2)}%`);
// Calculate average duration
const completedWithDuration = this.completed.filter(q => q.duration);
const averageDuration = completedWithDuration.length > 0
? completedWithDuration.reduce((sum, q) => sum + q.duration, 0) / completedWithDuration.length
: 0;
// Estimate remaining time
const remainingItems = pending + inProgress + retrying;
const estimatedTimeRemaining = remainingItems > 0 && averageDuration > 0
? (remainingItems * averageDuration) / this.options.maxConcurrent
: 0;
// System metrics (simplified)
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // MB
const cpuUsage = process.cpuUsage().user / 1000000; // seconds
return {
total,
pending,
inProgress,
completed,
failed,
retrying,
progress: Math.round(progress * 100) / 100,
averageDuration: Math.round(averageDuration),
estimatedTimeRemaining: Math.round(estimatedTimeRemaining),
activeWorkers: this.activeWorkers.size,
memoryUsage: Math.round(memoryUsage * 100) / 100,
cpuUsage: Math.round(cpuUsage * 100) / 100
};
}
calculatePriority(url) {
const pattern = this.options.priorityPatterns.find(p => url.includes(p.pattern));
return pattern ? pattern.priority : 1;
}
/**
* Start auto-save timer
*/
startAutoSave() {
if (!this.stateAdapter || !this.options.autoSaveInterval)
return;
this.autoSaveTimer = setInterval(async () => {
try {
await this.saveState();
}
catch (error) {
console.warn('Failed to auto-save queue state:', error instanceof Error ? error.message : String(error));
}
}, this.options.autoSaveInterval);
}
/**
* Stop auto-save timer
*/
stopAutoSave() {
if (this.autoSaveTimer) {
clearInterval(this.autoSaveTimer);
this.autoSaveTimer = null;
}
}
/**
* Create current queue state snapshot
*/
createStateSnapshot() {
const allUrls = [...this.queue, ...this.completed, ...this.failed];
return {
id: this.stateId,
urls: allUrls.map(q => q.url),
processedUrls: this.completed.map(q => q.url),
failedUrls: this.failed.map(q => q.url),
currentIndex: this.completed.length + this.failed.length,
totalUrls: allUrls.length,
results: this.completed.map(q => q.result),
startTime: this.startTime?.getTime() || Date.now(),
lastUpdateTime: Date.now(),
options: {
concurrency: this.options.maxConcurrent || 1,
retryLimit: this.options.maxRetries || 3,
...this.options
},
status: this.isProcessing ? 'processing' :
(allUrls.length === this.completed.length + this.failed.length ? 'completed' : 'paused'),
metadata: {
version: '1.0.0',
...this.options
}
};
}
/**
* Save current state to adapter
*/
async saveState() {
if (!this.stateAdapter || !this.options.enablePersistence)
return;
try {
this.currentState = this.createStateSnapshot();
await this.stateAdapter.save(this.currentState);
}
catch (error) {
throw new queue_state_1.QueueStateError('Failed to save queue state', error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Load state from adapter and resume processing
*/
async resumeFromState(options) {
if (!this.stateAdapter) {
this.stateAdapter = options?.adapter || new file_queue_state_adapter_1.FileQueueStateAdapter();
}
const stateId = options?.stateId || this.stateId;
try {
const state = await this.stateAdapter.load(stateId);
if (!state) {
throw new queue_state_1.QueueStateError(`Queue state not found: ${stateId}`);
}
// Restore queue state
this.stateId = state.id;
this.currentState = state;
this.startTime = new Date(state.startTime);
// Rebuild queue from state
const remainingUrls = options?.skipCompleted ?
state.urls.filter(url => !state.processedUrls.includes(url) && !state.failedUrls.includes(url)) :
state.urls;
this.queue = remainingUrls.map(url => ({
url,
priority: this.calculatePriority(url),
status: 'pending',
attempts: 0
}));
// Restore completed results
this.completed = state.processedUrls.map((url, index) => ({
url,
priority: this.calculatePriority(url),
status: 'completed',
attempts: 1,
result: state.results[index],
startedAt: new Date(state.startTime),
completedAt: new Date(state.lastUpdateTime),
duration: 1000 // Approximate
}));
// Restore failed results
this.failed = state.failedUrls.map(url => ({
url,
priority: this.calculatePriority(url),
status: 'failed',
attempts: this.options.maxRetries || 3,
error: 'Previously failed',
startedAt: new Date(state.startTime),
completedAt: new Date(state.lastUpdateTime),
duration: 1000 // Approximate
}));
// Update options from state if needed
this.options.maxConcurrent = state.options.concurrency;
this.options.maxRetries = state.options.retryLimit;
console.log(`β
Resumed queue state: ${this.completed.length} completed, ${this.queue.length} remaining`);
}
catch (error) {
throw new queue_state_1.QueueStateError(`Failed to resume from state: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* Delete saved state
*/
async deleteState(stateId) {
if (!this.stateAdapter)
return;
try {
await this.stateAdapter.delete(stateId || this.stateId);
}
catch (error) {
throw new queue_state_1.QueueStateError(`Failed to delete state: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : new Error(String(error)));
}
}
/**
* List all available saved states
*/
async listSavedStates() {
if (!this.stateAdapter)
return [];
try {
return await this.stateAdapter.list();
}
catch (error) {
console.warn('Failed to list saved states:', error instanceof Error ? error.message : String(error));
return [];
}
}
/**
* Get state ID
*/
getStateId() {
return this.stateId;
}
/**
* Check if persistence is enabled
*/
isPersistenceEnabled() {
return this.options.enablePersistence === true;
}
/**
* π Integrated parallel queue processing
* Processes all URLs in parallel with automatic status reporting
*/
async processUrls(urls, options) {
console.log(`π Starting queue processing for ${urls.length} URLs with ${this.options.maxConcurrent} workers`);
this.addUrls(urls);
this.startTime = new Date();
this.isProcessing = true; // Fix: Enable processing so workers don't exit early
// Save initial state if persistence is enabled
if (this.options.enablePersistence) {
try {
await this.saveState();
console.log(`πΎ Initial state saved with ID: ${this.stateId}`);
}
catch (error) {
console.warn('Failed to save initial state:', error instanceof Error ? error.message : String(error));
}
}
// Starte Status-Updates
if (this.options.enableShortStatus) {
this.startStatusUpdates(options.onShortStatus);
}
const results = [];
const promises = [];
// Erstelle Worker-Promises
for (let i = 0; i < this.options.maxConcurrent; i++) {
promises.push(this.worker(i, options));
}
// Warte auf alle Worker
await Promise.all(promises);
// Stoppe Status-Updates
this.stopStatusUpdates();
// Collect all results
results.push(...this.completed.map(q => q.result));
// Save final state if persistence is enabled
if (this.options.enablePersistence) {
try {
await this.saveState();
console.log(`πΎ Final state saved with ID: ${this.stateId}`);
}
catch (error) {
console.warn('Failed to save final state:', error instanceof Error ? error.message : String(error));
}
}
const duration = Date.now() - this.startTime.getTime();
console.log(`β
Queue processing completed: ${this.completed.length}/${urls.length} URLs in ${duration}ms`);
return results;
}
/**
* π§ Worker function for parallel processing (optimized to prevent timeout accumulation)
*/
async worker(workerId, options) {
let consecutiveIdleAttempts = 0;
const maxIdleAttempts = 20; // Exit after 2 seconds of idle (20 * 100ms)
console.log(`π Worker ${workerId} starting`);
while (this.isProcessing) {
const pendingUrls = this.queue.filter(q => q.status === 'pending' || q.status === 'retrying');
// Exit if no pending work remains
if (pendingUrls.length === 0) {
consecutiveIdleAttempts++;
// Quick exit if no work is available
if (consecutiveIdleAttempts > maxIdleAttempts) {
console.log(`π΄ Worker ${workerId} - no work available, exiting`);
break;
}
// Short wait before checking again
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
const queuedUrl = await this.getNextUrl();
if (!queuedUrl) {
consecutiveIdleAttempts++;
if (consecutiveIdleAttempts > maxIdleAttempts) {
console.log(`π΄ Worker ${workerId} idle timeout - exiting`);
break;
}
// Short wait before checking again
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
// Reset idle counter when work is found
consecutiveIdleAttempts = 0;
try {
const result = await options.processor(queuedUrl.url);
this.markCompleted(queuedUrl.url, result);
options.onResult?.(queuedUrl.url, result);
}
catch (error) {
this.markFailed(queuedUrl.url, String(error));
options.onError?.(queuedUrl.url, String(error));
}
}
console.log(`π Worker ${workerId} finished`);
}
/**
* π Starts short status updates
*/
startStatusUpdates(onShortStatus) {
this.statusInterval = setInterval(() => {
const stats = this.getStats();
const status = this.generateShortStatus(stats);
this.emit('short-status', status);
onShortStatus?.(status);
this.options.eventCallbacks?.onShortStatus?.(status);
}, this.options.statusUpdateInterval);
}
/**
* βΉοΈ Stops status updates
*/
stopStatusUpdates() {
if (this.statusInterval) {
clearInterval(this.statusInterval);
this.statusInterval = null;
}
}
/**
* π Generates short status message with improved ETA
*/
generateShortStatus(stats) {
const progress = Math.round(stats.progress);
const progressBar = this.createProgressBar(stats.progress, 20);
// Improved ETA calculation
let eta = '';
if (stats.estimatedTimeRemaining > 0) {
const seconds = Math.round(stats.estimatedTimeRemaining / 1000);
if (seconds < 60) {
eta = `ETA: ${seconds}s | `;
}
else if (seconds < 3600) {
eta = `ETA: ${Math.round(seconds / 60)}m | `;
}
else {
eta = `ETA: ${Math.round(seconds / 3600)}h | `;
}
}
// Speed indicator (URLs per minute)
const speed = stats.completed > 0 && this.startTime
? Math.round((stats.completed / ((Date.now() - this.startTime.getTime()) / 60000)) * 10) / 10
: 0;
const elapsed = this.startTime ? Math.round((Date.now() - this.startTime.getTime()) / 1000) : 0;
// Only return the status string - don't print here to avoid spam
return `π Testing pages... ${progressBar} ${progress}% (${stats.completed}/${stats.total})\n ${eta}Speed: ${speed.toFixed(1)} pages/min | Elapsed: ${elapsed}s`;
}
/**
* Create a simple progress bar
*/
createProgressBar(percentage, length = 20) {
const filled = Math.round((percentage / 100) * length);
return 'β'.repeat(filled) + 'β'.repeat(length - filled);
}
// Public API fΓΌr Event-Listener
onUrlAdded(callback) {
this.on('queue:urlAdded', callback);
return this;
}
onUrlStarted(callback) {
this.on('queue:urlStarted', callback);
return this;
}
onUrlCompleted(callback) {
this.on('queue:urlCompleted', callback);
return this;
}
onUrlFailed(callback) {
this.on('queue:urlFailed', callback);
return this;
}
onUrlRetrying(callback) {
this.on('queue:urlRetrying', callback);
return this;
}
onQueueEmpty(callback) {
this.on('queue:empty', callback);
return this;
}
onProgressUpdate(callback) {
this.on('queue:progressUpdate', callback);
return this;
}
onError(callback) {
this.on('queue:error', callback);
return this;
}
/**
* Cleanup resources and stop processing
*/
destroy() {
this.isProcessing = false;
// Stop monitoring
this.resourceMonitor?.stop();
this.resourceMonitor?.destroy();
this.backpressureController?.destroy();
// Stop persistence
this.stopAutoSave();
// Clear timeouts
for (const timeoutId of this.hardTimeoutTracker.values()) {
clearTimeout(timeoutId);
}
this.hardTimeoutTracker.clear();
if (this.globalTimeoutTimer) {
clearTimeout(this.globalTimeoutTimer);
this.globalTimeoutTimer = null;
}
this.stopStatusUpdates();
// Clear active workers
this.activeWorkers.clear();
// Remove all listeners
this.removeAllListeners();
}
/**
* Force garbage collection if available
*/
forceGarbageCollection() {
const beforeMB = process.memoryUsage().heapUsed / 1024 / 1024;
if (this.resourceMonitor?.forceGC()) {
const afterMB = process.memoryUsage().heapUsed / 1024 / 1024;
this.options.eventCallbacks?.onGarbageCollection?.(beforeMB, afterMB);
return true;
}
return this.backpressureController?.triggerGarbageCollection() || false;
}
// Utility-Methoden
getCompletedResults() {
return this.completed.map(q => q.result);
}
getFailedResults() {
return this.failed.map(q => ({ url: q.url, error: q.error, attempts: q.attempts }));
}
clear() {
this.queue = [];
this.completed = [];
this.failed = [];
this.activeWorkers.clear();
this.isProcessing = false;
this.startTime = null;
}
pause() {
this.isProcessing = false;
}
resume() {
this.isProcessing = true;
}
isPaused() {
return !this.isProcessing;
}
/**
* π§― Cleanup method to prevent memory leaks
*/
cleanup() {
// Stop any active status updates
this.stopStatusUpdates();
// Stop persistence
this.stopAutoSave();
// Clear all arrays and collections
this.queue = [];
this.completed = [];
this.failed = [];
this.activeWorkers.clear();
// Reset state
this.isProcessing = false;
this.startTime = null;
this.lastProgressUpdate = 0;
this.currentState = null;
// Remove all event listeners to prevent memory leaks
this.removeAllListeners();
console.log('π§― EventDrivenQueue cleaned up - memory leaks prevented');
}
getQueueSize() {
return this.queue.length;
}
getActiveWorkers() {
return this.activeWorkers.size;
}
getMaxConcurrent() {
return this.options.maxConcurrent;
}
setMaxConcurrent(max) {
this.options.maxConcurrent = max;
}
}
exports.EventDrivenQueue = EventDrivenQueue;
//# sourceMappingURL=event-driven-queue.js.map