tasq/node_modules/agentic-flow/dist/utils/connection-pool.js

185 lines
6.3 KiB
JavaScript

/**
* 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