UNPKG

@bcoders.gr/eth-provider

Version:

High-Performance IPC Ethereum Provider with Advanced Optimizations - Refactored for Better Performance

355 lines (307 loc) 10.7 kB
/** * High-Performance Request Object Pool * Optimized memory management for JSON-RPC request objects */ export class RequestPool { constructor(options = {}) { this.poolSize = options.poolSize || 100; this.maxPoolSize = options.maxPoolSize || 500; this.growthFactor = options.growthFactor || 1.5; // Object pools this.requestPool = []; this.timeoutPool = []; // Pool statistics this.stats = { created: 0, reused: 0, destroyed: 0, currentSize: 0, maxSizeReached: 0, poolHits: 0, poolMisses: 0 }; // Initialize pool this.initializePool(); this.logger = options.logger || console; } /** * Initialize the object pool with default objects */ initializePool() { for (let i = 0; i < this.poolSize; i++) { this.requestPool.push(this.createRequestObject()); this.timeoutPool.push(this.createTimeoutObject()); } this.stats.currentSize = this.poolSize; this.stats.created = this.poolSize * 2; // requests + timeouts } /** * Create a new request object template * @returns {object} Clean request object */ createRequestObject() { return { id: null, jsonrpc: '2.0', method: null, params: null, // Metadata for internal use _pooled: true, _timestamp: null, _priority: 'normal' }; } /** * Create a new timeout object template * @returns {object} Clean timeout tracking object */ createTimeoutObject() { return { timeoutId: null, requestId: null, startTime: null, duration: null, callback: null, _pooled: true }; } /** * Get a request object from the pool * @param {string} id - Request ID * @param {string} method - RPC method * @param {Array} params - Method parameters * @param {string} priority - Request priority * @returns {object} Request object */ getRequest(id, method, params = [], priority = 'normal') { let request; if (this.requestPool.length > 0) { request = this.requestPool.pop(); this.stats.poolHits++; this.stats.reused++; } else { // Pool exhausted, create new object request = this.createRequestObject(); this.stats.poolMisses++; this.stats.created++; // Consider growing the pool this.considerPoolGrowth(); } // Initialize request object request.id = id; request.method = method; request.params = params; request._timestamp = Date.now(); request._priority = priority; return request; } /** * Return a request object to the pool * @param {object} request - Request object to return */ returnRequest(request) { if (!request || !request._pooled) { return; // Not a pooled object } // Clean the object request.id = null; request.method = null; request.params = null; request._timestamp = null; request._priority = 'normal'; // Return to pool if there's space if (this.requestPool.length < this.maxPoolSize) { this.requestPool.push(request); } else { // Pool is full, let it be garbage collected this.stats.destroyed++; } } /** * Get a timeout tracking object from the pool * @param {string} requestId - Associated request ID * @param {number} duration - Timeout duration in ms * @param {Function} callback - Timeout callback * @returns {object} Timeout tracking object */ getTimeout(requestId, duration, callback) { let timeout; if (this.timeoutPool.length > 0) { timeout = this.timeoutPool.pop(); this.stats.poolHits++; this.stats.reused++; } else { timeout = this.createTimeoutObject(); this.stats.poolMisses++; this.stats.created++; } // Initialize timeout object timeout.requestId = requestId; timeout.startTime = Date.now(); timeout.duration = duration; timeout.callback = callback; return timeout; } /** * Return a timeout object to the pool * @param {object} timeout - Timeout object to return */ returnTimeout(timeout) { if (!timeout || !timeout._pooled) { return; } // Clean the object timeout.timeoutId = null; timeout.requestId = null; timeout.startTime = null; timeout.duration = null; timeout.callback = null; // Return to pool if there's space if (this.timeoutPool.length < this.maxPoolSize) { this.timeoutPool.push(timeout); } else { this.stats.destroyed++; } } /** * Consider growing the pool if needed */ considerPoolGrowth() { const currentTotal = this.requestPool.length + this.timeoutPool.length; if (currentTotal === 0 && this.stats.currentSize < this.maxPoolSize) { const growthSize = Math.min( Math.floor(this.stats.currentSize * (this.growthFactor - 1)), this.maxPoolSize - this.stats.currentSize ); if (growthSize > 0) { this.logger.log(`📈 Growing object pool by ${growthSize} objects`); for (let i = 0; i < growthSize; i++) { this.requestPool.push(this.createRequestObject()); this.timeoutPool.push(this.createTimeoutObject()); } this.stats.currentSize += growthSize; this.stats.created += growthSize * 2; if (this.stats.currentSize >= this.maxPoolSize) { this.stats.maxSizeReached++; } } } } /** * Shrink the pool to reduce memory usage * @param {number} targetSize - Target pool size (optional) */ shrinkPool(targetSize = this.poolSize) { const currentSize = this.requestPool.length + this.timeoutPool.length; const shrinkAmount = Math.max(0, currentSize - targetSize); if (shrinkAmount === 0) return; let removed = 0; // Remove from request pool while (this.requestPool.length > targetSize / 2 && removed < shrinkAmount) { this.requestPool.pop(); removed++; this.stats.destroyed++; } // Remove from timeout pool while (this.timeoutPool.length > targetSize / 2 && removed < shrinkAmount) { this.timeoutPool.pop(); removed++; this.stats.destroyed++; } this.stats.currentSize -= removed; this.logger.log(`📉 Shrunk object pool by ${removed} objects`); } /** * Get pool statistics * @returns {object} Pool statistics */ getStats() { const totalRequests = this.stats.poolHits + this.stats.poolMisses; const hitRatio = totalRequests > 0 ? (this.stats.poolHits / totalRequests) * 100 : 0; return { ...this.stats, requestPoolSize: this.requestPool.length, timeoutPoolSize: this.timeoutPool.length, totalPoolSize: this.requestPool.length + this.timeoutPool.length, hitRatio: parseFloat(hitRatio.toFixed(2)), efficiency: { reuseRatio: this.stats.created > 0 ? (this.stats.reused / this.stats.created) * 100 : 0, wasteRatio: this.stats.created > 0 ? (this.stats.destroyed / this.stats.created) * 100 : 0 } }; } /** * Optimize pool based on usage patterns */ optimize() { const stats = this.getStats(); // If hit ratio is low, consider growing the pool if (stats.hitRatio < 80 && this.stats.currentSize < this.maxPoolSize) { this.considerPoolGrowth(); } // If pools are mostly full and hit ratio is high, consider shrinking else if (stats.hitRatio > 95 && stats.totalPoolSize > this.poolSize * 1.5) { this.shrinkPool(Math.max(this.poolSize, stats.totalPoolSize * 0.8)); } } /** * Clear all pools and reset statistics */ clear() { this.requestPool = []; this.timeoutPool = []; this.stats = { created: 0, reused: 0, destroyed: 0, currentSize: 0, maxSizeReached: 0, poolHits: 0, poolMisses: 0 }; // Reinitialize with base pool size this.initializePool(); } /** * Validate pool integrity (for debugging) * @returns {object} Validation results */ validatePool() { const issues = []; // Check request pool for (let i = 0; i < this.requestPool.length; i++) { const req = this.requestPool[i]; if (!req._pooled) { issues.push(`Request pool item ${i} missing _pooled flag`); } if (req.id !== null || req.method !== null || req.params !== null) { issues.push(`Request pool item ${i} not properly cleaned`); } } // Check timeout pool for (let i = 0; i < this.timeoutPool.length; i++) { const timeout = this.timeoutPool[i]; if (!timeout._pooled) { issues.push(`Timeout pool item ${i} missing _pooled flag`); } if (timeout.requestId !== null || timeout.callback !== null) { issues.push(`Timeout pool item ${i} not properly cleaned`); } } return { valid: issues.length === 0, issues, requestPoolSize: this.requestPool.length, timeoutPoolSize: this.timeoutPool.length }; } /** * Destroy the pool and cleanup resources */ destroy() { this.requestPool = []; this.timeoutPool = []; this.stats.destroyed += this.stats.currentSize; this.stats.currentSize = 0; } }