UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

331 lines (330 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.concurrentOps = exports.ConcurrentOperationManager = void 0; /** * Concurrent operation handling with rate limiting and resource management */ const events_1 = require("events"); class ConcurrentOperationManager extends events_1.EventEmitter { constructor(maxConcurrent = 5, rateLimitWindow = 1000, maxOperationsPerWindow = 10) { super(); this.queue = []; this.running = new Map(); this.completed = []; this.operationTimes = []; this.maxConcurrent = maxConcurrent; this.rateLimitWindow = rateLimitWindow; this.maxOperationsPerWindow = maxOperationsPerWindow; this.startProcessing(); this.setupCleanup(); } static getInstance(maxConcurrent, rateLimitWindow, maxOperationsPerWindow) { if (!ConcurrentOperationManager.instance) { ConcurrentOperationManager.instance = new ConcurrentOperationManager(maxConcurrent, rateLimitWindow, maxOperationsPerWindow); } return ConcurrentOperationManager.instance; } /** * Execute an operation with rate limiting and concurrency control */ async execute(operation, options = {}) { return new Promise((resolve, reject) => { const id = this.generateId(); const queuedOp = { id, operation, options: { priority: 0, timeout: 30000, retries: 3, retryDelay: 1000, tags: [], ...options }, resolve, reject, timestamp: Date.now(), attempts: 0 }; // Add to queue with priority sorting this.addToQueue(queuedOp); this.emit('operationQueued', { id, queueSize: this.queue.length }); }); } /** * Execute multiple operations concurrently with limits */ async executeAll(operations, options = {}) { const promises = operations.map(op => this.execute(op, options)); return Promise.all(promises); } /** * Execute operations with different strategies */ async executeWithStrategy(operations, strategy = 'all', options = {}) { const promises = operations.map(op => this.execute(op, options)); switch (strategy) { case 'race': return Promise.race(promises); case 'allSettled': return Promise.allSettled(promises); case 'all': default: return Promise.all(promises); } } /** * Cancel operations by ID or tags */ cancel(idOrTags) { let cancelled = 0; if (typeof idOrTags === 'string') { // Cancel by ID const queueIndex = this.queue.findIndex(op => op.id === idOrTags); if (queueIndex !== -1) { const op = this.queue.splice(queueIndex, 1)[0]; op.reject(new Error('Operation cancelled')); cancelled++; } const runningOp = this.running.get(idOrTags); if (runningOp && runningOp.options.abortSignal) { runningOp.options.abortSignal.dispatchEvent(new Event('abort')); cancelled++; } } else { // Cancel by tags const tags = idOrTags; // Cancel queued operations this.queue = this.queue.filter(op => { const hasTag = op.options.tags?.some(tag => tags.includes(tag)); if (hasTag) { op.reject(new Error('Operation cancelled by tag')); cancelled++; return false; } return true; }); // Cancel running operations for (const [id, op] of this.running) { const hasTag = op.options.tags?.some(tag => tags.includes(tag)); if (hasTag && op.options.abortSignal) { op.options.abortSignal.dispatchEvent(new Event('abort')); cancelled++; } } } this.emit('operationsCancelled', { count: cancelled }); return cancelled; } /** * Get operation statistics */ getStats() { const total = this.completed.length + this.running.size + this.queue.length; const successful = this.completed.filter(op => op.success).length; const avgDuration = this.completed.length > 0 ? this.completed.reduce((sum, op) => sum + op.duration, 0) / this.completed.length : 0; return { total, pending: this.queue.length, running: this.running.size, completed: this.completed.length, failed: this.completed.length - successful, avgDuration, successRate: this.completed.length > 0 ? (successful / this.completed.length) * 100 : 0 }; } /** * Adjust concurrency limits */ setConcurrencyLimits(maxConcurrent, rateLimitWindow, maxOperationsPerWindow) { if (maxConcurrent !== undefined) { this.maxConcurrent = maxConcurrent; } if (rateLimitWindow !== undefined) { this.rateLimitWindow = rateLimitWindow; } if (maxOperationsPerWindow !== undefined) { this.maxOperationsPerWindow = maxOperationsPerWindow; } this.emit('limitsChanged', { maxConcurrent: this.maxConcurrent, rateLimitWindow: this.rateLimitWindow, maxOperationsPerWindow: this.maxOperationsPerWindow }); } /** * Get current rate limiting status */ getRateLimitStatus() { const now = Date.now(); const windowStart = now - this.rateLimitWindow; // Clean old operation times this.operationTimes = this.operationTimes.filter(time => time > windowStart); const canExecute = this.operationTimes.length < this.maxOperationsPerWindow; const nextSlotAvailable = this.operationTimes.length > 0 ? Math.max(0, this.operationTimes[0] + this.rateLimitWindow - now) : 0; return { currentWindow: this.rateLimitWindow, operationsInWindow: this.operationTimes.length, canExecute, nextSlotAvailable }; } /** * Add operation to queue with priority sorting */ addToQueue(operation) { // Insert with priority (higher priority first) let insertIndex = this.queue.length; for (let i = 0; i < this.queue.length; i++) { if ((operation.options.priority || 0) > (this.queue[i].options.priority || 0)) { insertIndex = i; break; } } this.queue.splice(insertIndex, 0, operation); } /** * Start processing operations from queue */ startProcessing() { setInterval(() => { this.processQueue(); }, 100); } /** * Process operations from queue */ async processQueue() { // Check if we can execute more operations if (this.running.size >= this.maxConcurrent) { return; } // Check rate limiting const rateLimitStatus = this.getRateLimitStatus(); if (!rateLimitStatus.canExecute) { return; } // Get next operation from queue const operation = this.queue.shift(); if (!operation) { return; } // Check if operation has timed out while waiting const waitTime = Date.now() - operation.timestamp; if (operation.options.timeout && waitTime > operation.options.timeout) { operation.reject(new Error('Operation timed out while waiting in queue')); return; } // Execute operation this.executeOperation(operation); } /** * Execute a single operation */ async executeOperation(operation) { const startTime = Date.now(); this.operationTimes.push(startTime); this.running.set(operation.id, operation); operation.attempts++; this.emit('operationStarted', { id: operation.id, attempt: operation.attempts, runningCount: this.running.size }); try { // Set up timeout if specified let timeoutId; const timeoutPromise = operation.options.timeout ? new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Operation timed out after ${operation.options.timeout}ms`)); }, operation.options.timeout); }) : null; // Execute operation with timeout const result = timeoutPromise ? await Promise.race([operation.operation(), timeoutPromise]) : await operation.operation(); if (timeoutId) { clearTimeout(timeoutId); } // Operation succeeded const duration = Date.now() - startTime; this.running.delete(operation.id); this.completed.push({ id: operation.id, duration, success: true }); operation.resolve(result); this.emit('operationCompleted', { id: operation.id, duration, success: true, attempts: operation.attempts }); } catch (error) { const duration = Date.now() - startTime; this.running.delete(operation.id); // Check if we should retry if (operation.attempts < (operation.options.retries || 0)) { // Retry after delay setTimeout(() => { this.addToQueue(operation); }, operation.options.retryDelay || 1000); this.emit('operationRetry', { id: operation.id, attempt: operation.attempts, error: error.message }); } else { // Operation failed permanently this.completed.push({ id: operation.id, duration, success: false }); operation.reject(error); this.emit('operationFailed', { id: operation.id, duration, attempts: operation.attempts, error: error.message }); } } } /** * Generate unique operation ID */ generateId() { return `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Setup cleanup and monitoring */ setupCleanup() { // Clean completed operations periodically setInterval(() => { if (this.completed.length > 1000) { this.completed = this.completed.slice(-500); } }, 60000); // Monitor for stuck operations setInterval(() => { const now = Date.now(); for (const [id, operation] of this.running) { const runtime = now - operation.timestamp; if (runtime > 300000) { // 5 minutes this.emit('operationStuck', { id, runtime, attempts: operation.attempts }); } } }, 30000); } } exports.ConcurrentOperationManager = ConcurrentOperationManager; // Export singleton instance exports.concurrentOps = ConcurrentOperationManager.getInstance();