@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
JavaScript
"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();