crawlforge-mcp-server 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CLAUDE.md +315 -0
  2. package/LICENSE +21 -0
  3. package/README.md +181 -0
  4. package/package.json +115 -0
  5. package/server.js +1963 -0
  6. package/setup.js +112 -0
  7. package/src/constants/config.js +615 -0
  8. package/src/core/ActionExecutor.js +1104 -0
  9. package/src/core/AlertNotificationSystem.js +601 -0
  10. package/src/core/AuthManager.js +315 -0
  11. package/src/core/ChangeTracker.js +2306 -0
  12. package/src/core/JobManager.js +687 -0
  13. package/src/core/LLMsTxtAnalyzer.js +753 -0
  14. package/src/core/LocalizationManager.js +1615 -0
  15. package/src/core/PerformanceManager.js +828 -0
  16. package/src/core/ResearchOrchestrator.js +1327 -0
  17. package/src/core/SnapshotManager.js +1037 -0
  18. package/src/core/StealthBrowserManager.js +1795 -0
  19. package/src/core/WebhookDispatcher.js +745 -0
  20. package/src/core/analysis/ContentAnalyzer.js +749 -0
  21. package/src/core/analysis/LinkAnalyzer.js +972 -0
  22. package/src/core/cache/CacheManager.js +821 -0
  23. package/src/core/connections/ConnectionPool.js +553 -0
  24. package/src/core/crawlers/BFSCrawler.js +845 -0
  25. package/src/core/integrations/PerformanceIntegration.js +377 -0
  26. package/src/core/llm/AnthropicProvider.js +135 -0
  27. package/src/core/llm/LLMManager.js +415 -0
  28. package/src/core/llm/LLMProvider.js +97 -0
  29. package/src/core/llm/OpenAIProvider.js +127 -0
  30. package/src/core/processing/BrowserProcessor.js +986 -0
  31. package/src/core/processing/ContentProcessor.js +505 -0
  32. package/src/core/processing/PDFProcessor.js +448 -0
  33. package/src/core/processing/StreamProcessor.js +673 -0
  34. package/src/core/queue/QueueManager.js +98 -0
  35. package/src/core/workers/WorkerPool.js +585 -0
  36. package/src/core/workers/worker.js +743 -0
  37. package/src/monitoring/healthCheck.js +600 -0
  38. package/src/monitoring/metrics.js +761 -0
  39. package/src/optimization/wave3-optimizations.js +932 -0
  40. package/src/security/security-patches.js +120 -0
  41. package/src/security/security-tests.js +355 -0
  42. package/src/security/wave3-security.js +652 -0
  43. package/src/tools/advanced/BatchScrapeTool.js +1089 -0
  44. package/src/tools/advanced/ScrapeWithActionsTool.js +669 -0
  45. package/src/tools/crawl/crawlDeep.js +449 -0
  46. package/src/tools/crawl/mapSite.js +400 -0
  47. package/src/tools/extract/analyzeContent.js +624 -0
  48. package/src/tools/extract/extractContent.js +329 -0
  49. package/src/tools/extract/processDocument.js +503 -0
  50. package/src/tools/extract/summarizeContent.js +376 -0
  51. package/src/tools/llmstxt/generateLLMsTxt.js +570 -0
  52. package/src/tools/research/deepResearch.js +706 -0
  53. package/src/tools/search/adapters/duckduckgoSearch.js +398 -0
  54. package/src/tools/search/adapters/googleSearch.js +236 -0
  55. package/src/tools/search/adapters/searchProviderFactory.js +96 -0
  56. package/src/tools/search/queryExpander.js +543 -0
  57. package/src/tools/search/ranking/ResultDeduplicator.js +676 -0
  58. package/src/tools/search/ranking/ResultRanker.js +497 -0
  59. package/src/tools/search/searchWeb.js +482 -0
  60. package/src/tools/tracking/trackChanges.js +1355 -0
  61. package/src/utils/CircuitBreaker.js +515 -0
  62. package/src/utils/ErrorHandlingConfig.js +342 -0
  63. package/src/utils/HumanBehaviorSimulator.js +569 -0
  64. package/src/utils/Logger.js +568 -0
  65. package/src/utils/MemoryMonitor.js +173 -0
  66. package/src/utils/RetryManager.js +386 -0
  67. package/src/utils/contentUtils.js +588 -0
  68. package/src/utils/domainFilter.js +612 -0
  69. package/src/utils/inputValidation.js +766 -0
  70. package/src/utils/rateLimiter.js +196 -0
  71. package/src/utils/robotsChecker.js +91 -0
  72. package/src/utils/securityMiddleware.js +416 -0
  73. package/src/utils/sitemapParser.js +678 -0
  74. package/src/utils/ssrfProtection.js +640 -0
  75. package/src/utils/urlNormalizer.js +168 -0
@@ -0,0 +1,98 @@
1
+ import PQueue from 'p-queue';
2
+
3
+ export class QueueManager {
4
+ constructor(options = {}) {
5
+ const {
6
+ concurrency = 10,
7
+ interval = 1000,
8
+ intervalCap = 10,
9
+ timeout = 30000
10
+ } = options;
11
+
12
+ this.queue = new PQueue({
13
+ concurrency,
14
+ interval,
15
+ intervalCap,
16
+ timeout,
17
+ throwOnTimeout: true
18
+ });
19
+
20
+ this.stats = {
21
+ processed: 0,
22
+ failed: 0,
23
+ pending: 0,
24
+ active: 0
25
+ };
26
+
27
+ this.setupEventHandlers();
28
+ }
29
+
30
+ setupEventHandlers() {
31
+ this.queue.on('active', () => {
32
+ this.stats.active = this.queue.pending;
33
+ this.stats.pending = this.queue.size;
34
+ });
35
+
36
+ this.queue.on('completed', () => {
37
+ this.stats.processed++;
38
+ });
39
+
40
+ this.queue.on('error', (error) => {
41
+ this.stats.failed++;
42
+ console.error('Queue error:', error);
43
+ });
44
+ }
45
+
46
+ async add(fn, options = {}) {
47
+ const { priority = 0 } = options;
48
+ return this.queue.add(fn, { priority });
49
+ }
50
+
51
+ async addAll(tasks, options = {}) {
52
+ const promises = tasks.map(task => this.add(task, options));
53
+ return Promise.all(promises);
54
+ }
55
+
56
+ pause() {
57
+ this.queue.pause();
58
+ }
59
+
60
+ start() {
61
+ this.queue.start();
62
+ }
63
+
64
+ clear() {
65
+ this.queue.clear();
66
+ }
67
+
68
+ async onEmpty() {
69
+ return this.queue.onEmpty();
70
+ }
71
+
72
+ async onIdle() {
73
+ return this.queue.onIdle();
74
+ }
75
+
76
+ getStats() {
77
+ return {
78
+ ...this.stats,
79
+ size: this.queue.size,
80
+ pending: this.queue.pending,
81
+ isPaused: this.queue.isPaused
82
+ };
83
+ }
84
+
85
+ get size() {
86
+ return this.queue.size;
87
+ }
88
+
89
+ get pending() {
90
+ return this.queue.pending;
91
+ }
92
+
93
+ get isPaused() {
94
+ return this.queue.isPaused;
95
+ }
96
+ }
97
+
98
+ export default QueueManager;
@@ -0,0 +1,585 @@
1
+ /**
2
+ * WorkerPool - Manages a pool of worker threads for CPU-intensive tasks
3
+ * Integrates with QueueManager for efficient task distribution
4
+ */
5
+
6
+ import { Worker } from 'worker_threads';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
9
+ import { EventEmitter } from 'events';
10
+ import { config } from '../../constants/config.js';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ export class WorkerPool extends EventEmitter {
16
+ constructor(options = {}) {
17
+ super();
18
+
19
+ const {
20
+ maxWorkers = config.performance.maxWorkers,
21
+ workerScript = join(__dirname, 'worker.js'),
22
+ taskTimeout = 30000,
23
+ idleTimeout = 60000,
24
+ retryAttempts = 3
25
+ } = options;
26
+
27
+ this.maxWorkers = maxWorkers;
28
+ this.workerScript = workerScript;
29
+ this.taskTimeout = taskTimeout;
30
+ this.idleTimeout = idleTimeout;
31
+ this.retryAttempts = retryAttempts;
32
+
33
+ // Worker management
34
+ this.workers = new Set();
35
+ this.availableWorkers = [];
36
+ this.busyWorkers = new Map();
37
+ this.taskQueue = [];
38
+
39
+ // Statistics
40
+ this.stats = {
41
+ tasksCompleted: 0,
42
+ tasksFailed: 0,
43
+ tasksQueued: 0,
44
+ workersCreated: 0,
45
+ workersDestroyed: 0,
46
+ avgTaskDuration: 0,
47
+ peakWorkerCount: 0
48
+ };
49
+
50
+ // Task tracking
51
+ this.activeTasks = new Map();
52
+ this.taskIdCounter = 0;
53
+
54
+ // Cleanup interval
55
+ this.cleanupInterval = setInterval(() => {
56
+ this.cleanupIdleWorkers();
57
+ }, this.idleTimeout / 2);
58
+
59
+ this.setupGracefulShutdown();
60
+ }
61
+
62
+ /**
63
+ * Execute a task using the worker pool
64
+ * @param {string|Object} taskType - Type of task to execute, or task object with {type, data, options}
65
+ * @param {any} data - Task data
66
+ * @param {Object} options - Task options
67
+ * @returns {Promise<any>} - Task result
68
+ */
69
+ async execute(taskType, data, options = {}) {
70
+ // Handle object-style task input from PerformanceManager
71
+ if (typeof taskType === 'object' && taskType.type) {
72
+ const taskObj = taskType;
73
+ taskType = taskObj.type;
74
+ data = taskObj.data;
75
+ options = taskObj.options || {};
76
+ }
77
+ const taskId = this.generateTaskId();
78
+ const { timeout = this.taskTimeout, priority = 0, retries = this.retryAttempts } = options;
79
+
80
+ const task = {
81
+ id: taskId,
82
+ type: taskType,
83
+ data,
84
+ timeout,
85
+ priority,
86
+ retries,
87
+ startTime: Date.now(),
88
+ attempts: 0
89
+ };
90
+
91
+ this.stats.tasksQueued++;
92
+ this.emit('taskQueued', { taskId, taskType });
93
+
94
+ return new Promise((resolve, reject) => {
95
+ task.resolve = resolve;
96
+ task.reject = reject;
97
+
98
+ // Add to queue or execute immediately if worker available
99
+ if (this.availableWorkers.length > 0) {
100
+ this.executeTask(task);
101
+ } else {
102
+ this.addToQueue(task);
103
+ this.maybeCreateWorker();
104
+ }
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Execute multiple tasks in parallel with optional batching
110
+ * @param {Array} tasks - Array of {taskType, data, options} objects
111
+ * @param {Object} batchOptions - Batching options
112
+ * @returns {Promise<Array>} - Array of results
113
+ */
114
+ async executeBatch(tasks, batchOptions = {}) {
115
+ const { maxConcurrent = this.maxWorkers, failFast = false } = batchOptions;
116
+
117
+ const chunks = this.chunkArray(tasks, maxConcurrent);
118
+ const results = [];
119
+
120
+ for (const chunk of chunks) {
121
+ const chunkPromises = chunk.map(({ taskType, data, options }) =>
122
+ this.execute(taskType, data, options).catch(error => {
123
+ if (failFast) throw error;
124
+ return { error: error.message };
125
+ })
126
+ );
127
+
128
+ const chunkResults = await Promise.all(chunkPromises);
129
+ results.push(...chunkResults);
130
+
131
+ if (failFast && chunkResults.some(result => result && result.error)) {
132
+ throw new Error('Batch execution failed fast');
133
+ }
134
+ }
135
+
136
+ return results;
137
+ }
138
+
139
+ /**
140
+ * Add task to priority queue
141
+ * @param {Object} task - Task object
142
+ */
143
+ addToQueue(task) {
144
+ // Insert task in priority order (higher priority first)
145
+ let insertIndex = this.taskQueue.length;
146
+ for (let i = 0; i < this.taskQueue.length; i++) {
147
+ if (this.taskQueue[i].priority < task.priority) {
148
+ insertIndex = i;
149
+ break;
150
+ }
151
+ }
152
+ this.taskQueue.splice(insertIndex, 0, task);
153
+ }
154
+
155
+ /**
156
+ * Execute a task using an available worker
157
+ * @param {Object} task - Task object
158
+ */
159
+ async executeTask(task) {
160
+ let worker = this.getAvailableWorker();
161
+
162
+ if (!worker) {
163
+ // Create new worker if under limit
164
+ if (this.workers.size < this.maxWorkers) {
165
+ worker = await this.createWorker();
166
+ } else {
167
+ // Queue the task
168
+ this.addToQueue(task);
169
+ return;
170
+ }
171
+ }
172
+
173
+ this.assignTaskToWorker(task, worker);
174
+ }
175
+
176
+ /**
177
+ * Assign a task to a specific worker
178
+ * @param {Object} task - Task object
179
+ * @param {Worker} worker - Worker instance
180
+ */
181
+ assignTaskToWorker(task, worker) {
182
+ task.attempts++;
183
+ task.worker = worker;
184
+
185
+ // Remove worker from available pool
186
+ const workerIndex = this.availableWorkers.indexOf(worker);
187
+ if (workerIndex !== -1) {
188
+ this.availableWorkers.splice(workerIndex, 1);
189
+ }
190
+
191
+ // Add to busy workers
192
+ this.busyWorkers.set(worker, task);
193
+ this.activeTasks.set(task.id, task);
194
+
195
+ // Set up timeout
196
+ const timeoutId = setTimeout(() => {
197
+ this.handleTaskTimeout(task);
198
+ }, task.timeout);
199
+
200
+ task.timeoutId = timeoutId;
201
+
202
+ // Send task to worker
203
+ worker.postMessage({
204
+ taskId: task.id,
205
+ type: task.type,
206
+ data: task.data
207
+ });
208
+
209
+ this.emit('taskStarted', {
210
+ taskId: task.id,
211
+ taskType: task.type,
212
+ workerId: worker.threadId
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Handle task completion
218
+ * @param {Object} task - Task object
219
+ * @param {any} result - Task result
220
+ * @param {Error} error - Task error (if any)
221
+ */
222
+ handleTaskCompletion(task, result, error) {
223
+ const duration = Date.now() - task.startTime;
224
+
225
+ // Update statistics
226
+ this.updateTaskStats(duration, !error);
227
+
228
+ // Clear timeout
229
+ if (task.timeoutId) {
230
+ clearTimeout(task.timeoutId);
231
+ }
232
+
233
+ // Remove from active tasks
234
+ this.activeTasks.delete(task.id);
235
+
236
+ // Return worker to available pool
237
+ if (task.worker) {
238
+ this.busyWorkers.delete(task.worker);
239
+ this.availableWorkers.push(task.worker);
240
+ task.worker.lastUsed = Date.now();
241
+ }
242
+
243
+ // Resolve or reject the task promise
244
+ if (error && task.attempts < task.retries) {
245
+ // Retry the task
246
+ setTimeout(() => {
247
+ this.executeTask(task);
248
+ }, 1000 * task.attempts); // Exponential backoff
249
+ } else if (error) {
250
+ task.reject(error);
251
+ this.emit('taskFailed', {
252
+ taskId: task.id,
253
+ taskType: task.type,
254
+ error: error.message,
255
+ attempts: task.attempts
256
+ });
257
+ } else {
258
+ task.resolve(result);
259
+ this.emit('taskCompleted', {
260
+ taskId: task.id,
261
+ taskType: task.type,
262
+ duration
263
+ });
264
+ }
265
+
266
+ // Process next task in queue
267
+ this.processNextTask();
268
+ }
269
+
270
+ /**
271
+ * Handle task timeout
272
+ * @param {Object} task - Task object
273
+ */
274
+ handleTaskTimeout(task) {
275
+ const error = new Error(`Task ${task.id} timed out after ${task.timeout}ms`);
276
+
277
+ // Terminate the worker if it's unresponsive
278
+ if (task.worker) {
279
+ this.terminateWorker(task.worker);
280
+ }
281
+
282
+ this.handleTaskCompletion(task, null, error);
283
+ }
284
+
285
+ /**
286
+ * Process the next task in the queue
287
+ */
288
+ processNextTask() {
289
+ if (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
290
+ const nextTask = this.taskQueue.shift();
291
+ this.executeTask(nextTask);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get an available worker from the pool
297
+ * @returns {Worker|null} - Available worker or null
298
+ */
299
+ getAvailableWorker() {
300
+ return this.availableWorkers.length > 0 ? this.availableWorkers[0] : null;
301
+ }
302
+
303
+ /**
304
+ * Create a new worker
305
+ * @returns {Promise<Worker>} - Worker instance
306
+ */
307
+ async createWorker() {
308
+ return new Promise((resolve, reject) => {
309
+ const worker = new Worker(this.workerScript);
310
+
311
+ worker.on('message', (message) => {
312
+ this.handleWorkerMessage(worker, message);
313
+ });
314
+
315
+ worker.on('error', (error) => {
316
+ this.handleWorkerError(worker, error);
317
+ });
318
+
319
+ worker.on('exit', (code) => {
320
+ this.handleWorkerExit(worker, code);
321
+ });
322
+
323
+ worker.on('online', () => {
324
+ this.workers.add(worker);
325
+ this.availableWorkers.push(worker);
326
+ worker.createdAt = Date.now();
327
+ worker.lastUsed = Date.now();
328
+
329
+ this.stats.workersCreated++;
330
+ this.stats.peakWorkerCount = Math.max(this.stats.peakWorkerCount, this.workers.size);
331
+
332
+ this.emit('workerCreated', { workerId: worker.threadId });
333
+ resolve(worker);
334
+ });
335
+
336
+ // Set creation timeout
337
+ const timeout = setTimeout(() => {
338
+ reject(new Error('Worker creation timeout'));
339
+ }, 10000);
340
+
341
+ worker.once('online', () => {
342
+ clearTimeout(timeout);
343
+ });
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Handle worker message
349
+ * @param {Worker} worker - Worker instance
350
+ * @param {Object} message - Message from worker
351
+ */
352
+ handleWorkerMessage(worker, message) {
353
+ // Handle worker ready signal
354
+ if (message && message.type === 'ready') {
355
+ return; // Ignore ready signals
356
+ }
357
+
358
+ const { taskId, result, error } = message;
359
+ const task = this.activeTasks.get(taskId);
360
+
361
+ if (!task) {
362
+ console.warn(`Received message for unknown task: ${taskId}`);
363
+ return;
364
+ }
365
+
366
+ const taskError = error ? new Error(error) : null;
367
+ this.handleTaskCompletion(task, result, taskError);
368
+ }
369
+
370
+ /**
371
+ * Handle worker error
372
+ * @param {Worker} worker - Worker instance
373
+ * @param {Error} error - Worker error
374
+ */
375
+ handleWorkerError(worker, error) {
376
+ console.error(`Worker ${worker.threadId} error:`, error);
377
+
378
+ const task = this.busyWorkers.get(worker);
379
+ if (task) {
380
+ this.handleTaskCompletion(task, null, error);
381
+ }
382
+
383
+ this.terminateWorker(worker);
384
+ }
385
+
386
+ /**
387
+ * Handle worker exit
388
+ * @param {Worker} worker - Worker instance
389
+ * @param {number} code - Exit code
390
+ */
391
+ handleWorkerExit(worker, code) {
392
+ this.workers.delete(worker);
393
+ this.stats.workersDestroyed++;
394
+
395
+ // Remove from available workers
396
+ const availableIndex = this.availableWorkers.indexOf(worker);
397
+ if (availableIndex !== -1) {
398
+ this.availableWorkers.splice(availableIndex, 1);
399
+ }
400
+
401
+ // Handle any active task
402
+ const task = this.busyWorkers.get(worker);
403
+ if (task) {
404
+ const error = new Error(`Worker exited with code ${code}`);
405
+ this.handleTaskCompletion(task, null, error);
406
+ }
407
+
408
+ this.emit('workerExited', { workerId: worker.threadId, code });
409
+ }
410
+
411
+ /**
412
+ * Terminate a worker
413
+ * @param {Worker} worker - Worker instance
414
+ */
415
+ async terminateWorker(worker) {
416
+ try {
417
+ await worker.terminate();
418
+ } catch (error) {
419
+ console.error(`Error terminating worker ${worker.threadId}:`, error);
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Create a new worker if conditions are met
425
+ */
426
+ maybeCreateWorker() {
427
+ if (this.workers.size < this.maxWorkers &&
428
+ this.taskQueue.length > 0 &&
429
+ this.availableWorkers.length === 0) {
430
+ this.createWorker().catch(error => {
431
+ console.error('Failed to create worker:', error);
432
+ });
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Clean up idle workers
438
+ */
439
+ cleanupIdleWorkers() {
440
+ const now = Date.now();
441
+ const workersToTerminate = [];
442
+
443
+ for (const worker of this.availableWorkers) {
444
+ if (now - worker.lastUsed > this.idleTimeout) {
445
+ workersToTerminate.push(worker);
446
+ }
447
+ }
448
+
449
+ // Keep at least one worker alive
450
+ if (this.availableWorkers.length - workersToTerminate.length < 1) {
451
+ workersToTerminate.pop();
452
+ }
453
+
454
+ for (const worker of workersToTerminate) {
455
+ this.terminateWorker(worker);
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Update task statistics
461
+ * @param {number} duration - Task duration
462
+ * @param {boolean} success - Whether task succeeded
463
+ */
464
+ updateTaskStats(duration, success) {
465
+ if (success) {
466
+ this.stats.tasksCompleted++;
467
+ } else {
468
+ this.stats.tasksFailed++;
469
+ }
470
+
471
+ // Update average duration
472
+ const totalTasks = this.stats.tasksCompleted + this.stats.tasksFailed;
473
+ this.stats.avgTaskDuration = (
474
+ (this.stats.avgTaskDuration * (totalTasks - 1) + duration) / totalTasks
475
+ );
476
+ }
477
+
478
+ /**
479
+ * Generate unique task ID
480
+ * @returns {string} - Task ID
481
+ */
482
+ generateTaskId() {
483
+ return `task_${++this.taskIdCounter}_${Date.now()}`;
484
+ }
485
+
486
+ /**
487
+ * Split array into chunks
488
+ * @param {Array} array - Array to chunk
489
+ * @param {number} chunkSize - Size of each chunk
490
+ * @returns {Array} - Array of chunks
491
+ */
492
+ chunkArray(array, chunkSize) {
493
+ const chunks = [];
494
+ for (let i = 0; i < array.length; i += chunkSize) {
495
+ chunks.push(array.slice(i, i + chunkSize));
496
+ }
497
+ return chunks;
498
+ }
499
+
500
+ /**
501
+ * Get worker pool statistics
502
+ * @returns {Object} - Statistics object
503
+ */
504
+ getStats() {
505
+ return {
506
+ ...this.stats,
507
+ activeWorkers: this.workers.size,
508
+ availableWorkers: this.availableWorkers.length,
509
+ busyWorkers: this.busyWorkers.size,
510
+ queuedTasks: this.taskQueue.length,
511
+ activeTasks: this.activeTasks.size
512
+ };
513
+ }
514
+
515
+ /**
516
+ * Pause the worker pool
517
+ */
518
+ pause() {
519
+ this.paused = true;
520
+ this.emit('paused');
521
+ }
522
+
523
+ /**
524
+ * Resume the worker pool
525
+ */
526
+ resume() {
527
+ this.paused = false;
528
+ this.emit('resumed');
529
+
530
+ // Process any queued tasks
531
+ while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
532
+ this.processNextTask();
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Gracefully shutdown the worker pool
538
+ * @returns {Promise<void>}
539
+ */
540
+ async shutdown() {
541
+ this.emit('shutdown');
542
+
543
+ // Clear cleanup interval
544
+ if (this.cleanupInterval) {
545
+ clearInterval(this.cleanupInterval);
546
+ }
547
+
548
+ // Wait for active tasks to complete or timeout
549
+ const shutdownTimeout = 30000; // 30 seconds
550
+ const startTime = Date.now();
551
+
552
+ while (this.activeTasks.size > 0 && (Date.now() - startTime) < shutdownTimeout) {
553
+ await new Promise(resolve => setTimeout(resolve, 100));
554
+ }
555
+
556
+ // Terminate all workers
557
+ const terminationPromises = Array.from(this.workers).map(worker =>
558
+ this.terminateWorker(worker)
559
+ );
560
+
561
+ await Promise.all(terminationPromises);
562
+
563
+ this.workers.clear();
564
+ this.availableWorkers.length = 0;
565
+ this.busyWorkers.clear();
566
+ this.activeTasks.clear();
567
+ this.taskQueue.length = 0;
568
+ }
569
+
570
+ /**
571
+ * Setup graceful shutdown handlers
572
+ */
573
+ setupGracefulShutdown() {
574
+ const shutdown = async () => {
575
+ console.log('WorkerPool: Graceful shutdown initiated');
576
+ await this.shutdown();
577
+ process.exit(0);
578
+ };
579
+
580
+ process.on('SIGTERM', shutdown);
581
+ process.on('SIGINT', shutdown);
582
+ }
583
+ }
584
+
585
+ export default WorkerPool;