/** * Connection Pool for HTTP/2 and HTTP/3 Proxies * Provides connection reuse to reduce latency by 20-30% */ import http2 from 'http2'; import { logger } from './logger.js'; export class ConnectionPool { pools = new Map(); config; cleanupInterval; constructor(config = {}) { this.config = { maxSize: config.maxSize || 10, maxIdleTime: config.maxIdleTime || 60000, // 60 seconds acquireTimeout: config.acquireTimeout || 5000 // 5 seconds }; // Cleanup expired connections every 30 seconds this.cleanupInterval = setInterval(() => { this.cleanup(); }, 30000); } async acquire(host) { const pool = this.pools.get(host) || []; const now = Date.now(); // Find idle, non-expired connection const idle = pool.find(c => !c.busy && !this.isExpired(c, now) && !c.session.closed && !c.session.destroyed); if (idle) { idle.busy = true; idle.lastUsed = now; logger.debug('Reusing pooled connection', { host, poolSize: pool.length }); return idle.session; } // Create new if under limit if (pool.length < this.config.maxSize) { const session = await this.createConnection(host); const conn = { session, host, busy: true, createdAt: now, lastUsed: now }; pool.push(conn); this.pools.set(host, pool); logger.debug('Created new pooled connection', { host, poolSize: pool.length, maxSize: this.config.maxSize }); return session; } // Wait for available connection logger.debug('Pool full, waiting for connection', { host, poolSize: pool.length }); return this.waitForConnection(host); } async release(session, host) { const pool = this.pools.get(host); if (!pool) return; const conn = pool.find(c => c.session === session); if (conn) { conn.busy = false; conn.lastUsed = Date.now(); logger.debug('Released pooled connection', { host }); } } async createConnection(host) { return new Promise((resolve, reject) => { const session = http2.connect(host, { maxSessionMemory: 10 // 10MB per session }); session.once('connect', () => { logger.info('HTTP/2 session connected', { host }); resolve(session); }); session.once('error', (error) => { logger.error('HTTP/2 session error', { host, error: error.message }); reject(error); }); // Cleanup on session close session.once('close', () => { this.removeConnection(session, host); }); }); } async waitForConnection(host) { const startTime = Date.now(); return new Promise((resolve, reject) => { const checkInterval = setInterval(() => { const pool = this.pools.get(host); if (!pool) { clearInterval(checkInterval); reject(new Error('Pool disappeared')); return; } const now = Date.now(); const available = pool.find(c => !c.busy && !this.isExpired(c, now) && !c.session.closed); if (available) { clearInterval(checkInterval); available.busy = true; available.lastUsed = now; resolve(available.session); return; } if (now - startTime > this.config.acquireTimeout) { clearInterval(checkInterval); reject(new Error('Connection acquire timeout')); } }, 100); // Check every 100ms }); } isExpired(conn, now) { return (now - conn.lastUsed) > this.config.maxIdleTime; } removeConnection(session, host) { const pool = this.pools.get(host); if (!pool) return; const index = pool.findIndex(c => c.session === session); if (index !== -1) { pool.splice(index, 1); logger.debug('Removed closed connection from pool', { host, poolSize: pool.length }); } if (pool.length === 0) { this.pools.delete(host); } } cleanup() { const now = Date.now(); let removed = 0; for (const [host, pool] of this.pools.entries()) { const before = pool.length; // Remove expired and closed connections const active = pool.filter(c => { if (this.isExpired(c, now) || c.session.closed || c.session.destroyed) { if (!c.session.closed) { c.session.close(); } return false; } return true; }); removed += before - active.length; if (active.length === 0) { this.pools.delete(host); } else { this.pools.set(host, active); } } if (removed > 0) { logger.debug('Cleaned up expired connections', { removed }); } } destroy() { clearInterval(this.cleanupInterval); for (const [host, pool] of this.pools.entries()) { for (const conn of pool) { if (!conn.session.closed) { conn.session.close(); } } } this.pools.clear(); logger.info('Connection pool destroyed'); } getStats() { const stats = {}; for (const [host, pool] of this.pools.entries()) { const busy = pool.filter(c => c.busy).length; stats[host] = { total: pool.length, busy, idle: pool.length - busy }; } return stats; } } //# sourceMappingURL=connection-pool.js.map