@push.rocks/smartproxy
Version:
A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.
325 lines • 25 kB
JavaScript
import { LifecycleComponent } from './lifecycle-component.js';
import { BinaryHeap } from './binary-heap.js';
import { AsyncMutex } from './async-utils.js';
import { EventEmitter } from 'events';
/**
* Enhanced connection pool with priority queue, backpressure, and lifecycle management
*/
export class EnhancedConnectionPool extends LifecycleComponent {
constructor(options) {
super();
this.availableConnections = [];
this.activeConnections = new Map();
this.mutex = new AsyncMutex();
this.eventEmitter = new EventEmitter();
this.connectionIdCounter = 0;
this.requestIdCounter = 0;
this.isClosing = false;
// Metrics
this.metrics = {
connectionsCreated: 0,
connectionsDestroyed: 0,
connectionsAcquired: 0,
connectionsReleased: 0,
acquireTimeouts: 0,
validationFailures: 0,
queueHighWaterMark: 0,
};
this.options = {
minSize: 0,
maxSize: 10,
acquireTimeout: 30000,
idleTimeout: 300000, // 5 minutes
maxUseCount: Infinity,
validateOnAcquire: true,
validateOnReturn: false,
queueTimeout: 60000,
...options,
};
// Initialize priority queue (higher priority = extracted first)
this.waitQueue = new BinaryHeap((a, b) => b.priority - a.priority || a.timestamp - b.timestamp, (item) => item.id);
// Start maintenance cycle
this.startMaintenance();
// Initialize minimum connections
this.initializeMinConnections();
}
/**
* Initialize minimum number of connections
*/
async initializeMinConnections() {
const promises = [];
for (let i = 0; i < this.options.minSize; i++) {
promises.push(this.createConnection()
.then(conn => {
this.availableConnections.push(conn);
})
.catch(err => {
if (this.options.onConnectionError) {
this.options.onConnectionError(err);
}
}));
}
await Promise.all(promises);
}
/**
* Start maintenance timer for idle connection cleanup
*/
startMaintenance() {
this.setInterval(() => {
this.performMaintenance();
}, 30000); // Every 30 seconds
}
/**
* Perform maintenance tasks
*/
async performMaintenance() {
await this.mutex.runExclusive(async () => {
const now = Date.now();
const toRemove = [];
// Check for idle connections beyond minimum size
for (let i = this.availableConnections.length - 1; i >= 0; i--) {
const conn = this.availableConnections[i];
// Keep minimum connections
if (this.availableConnections.length <= this.options.minSize) {
break;
}
// Remove idle connections
if (now - conn.lastUsedAt > this.options.idleTimeout) {
toRemove.push(conn);
this.availableConnections.splice(i, 1);
}
}
// Destroy idle connections
for (const conn of toRemove) {
await this.destroyConnection(conn);
}
});
}
/**
* Acquire a connection from the pool
*/
async acquire(priority = 0, timeout) {
if (this.isClosing) {
throw new Error('Connection pool is closing');
}
return this.mutex.runExclusive(async () => {
// Try to get an available connection
const connection = await this.tryAcquireConnection();
if (connection) {
return connection;
}
// Check if we can create a new connection
const totalConnections = this.availableConnections.length + this.activeConnections.size;
if (totalConnections < this.options.maxSize) {
try {
const newConnection = await this.createConnection();
return this.checkoutConnection(newConnection);
}
catch (err) {
// Fall through to queue if creation fails
}
}
// Add to wait queue
return this.queueAcquireRequest(priority, timeout);
});
}
/**
* Try to acquire an available connection
*/
async tryAcquireConnection() {
while (this.availableConnections.length > 0) {
const connection = this.availableConnections.shift();
// Check if connection exceeded max use count
if (connection.useCount >= this.options.maxUseCount) {
await this.destroyConnection(connection);
continue;
}
// Validate connection if required
if (this.options.validateOnAcquire && this.options.connectionValidator) {
try {
const isValid = await this.options.connectionValidator(connection.connection);
if (!isValid) {
this.metrics.validationFailures++;
await this.destroyConnection(connection);
continue;
}
}
catch (err) {
this.metrics.validationFailures++;
await this.destroyConnection(connection);
continue;
}
}
return this.checkoutConnection(connection);
}
return null;
}
/**
* Checkout a connection for use
*/
checkoutConnection(connection) {
connection.inUse = true;
connection.lastUsedAt = Date.now();
connection.useCount++;
this.activeConnections.set(connection.id, connection);
this.metrics.connectionsAcquired++;
this.eventEmitter.emit('acquire', connection);
return connection;
}
/**
* Queue an acquire request
*/
queueAcquireRequest(priority, timeout) {
return new Promise((resolve, reject) => {
const request = {
id: `req-${this.requestIdCounter++}`,
priority,
timestamp: Date.now(),
resolve,
reject,
};
// Set timeout
const timeoutMs = timeout || this.options.queueTimeout;
request.timeoutHandle = this.setTimeout(() => {
if (this.waitQueue.extractByKey(request.id)) {
this.metrics.acquireTimeouts++;
reject(new Error(`Connection acquire timeout after ${timeoutMs}ms`));
}
}, timeoutMs);
this.waitQueue.insert(request);
this.metrics.queueHighWaterMark = Math.max(this.metrics.queueHighWaterMark, this.waitQueue.size);
this.eventEmitter.emit('enqueue', { queueSize: this.waitQueue.size });
});
}
/**
* Release a connection back to the pool
*/
async release(connection) {
return this.mutex.runExclusive(async () => {
if (!connection.inUse || !this.activeConnections.has(connection.id)) {
throw new Error('Connection is not active');
}
this.activeConnections.delete(connection.id);
connection.inUse = false;
connection.lastUsedAt = Date.now();
this.metrics.connectionsReleased++;
// Check if connection should be destroyed
if (connection.useCount >= this.options.maxUseCount) {
await this.destroyConnection(connection);
return;
}
// Validate on return if required
if (this.options.validateOnReturn && this.options.connectionValidator) {
try {
const isValid = await this.options.connectionValidator(connection.connection);
if (!isValid) {
await this.destroyConnection(connection);
return;
}
}
catch (err) {
await this.destroyConnection(connection);
return;
}
}
// Check if there are waiting requests
const request = this.waitQueue.extract();
if (request) {
this.clearTimeout(request.timeoutHandle);
request.resolve(this.checkoutConnection(connection));
this.eventEmitter.emit('dequeue', { queueSize: this.waitQueue.size });
}
else {
// Return to available pool
this.availableConnections.push(connection);
this.eventEmitter.emit('release', connection);
}
});
}
/**
* Create a new connection
*/
async createConnection() {
const rawConnection = await this.options.connectionFactory();
const connection = {
id: `conn-${this.connectionIdCounter++}`,
connection: rawConnection,
createdAt: Date.now(),
lastUsedAt: Date.now(),
useCount: 0,
inUse: false,
};
this.metrics.connectionsCreated++;
this.eventEmitter.emit('create', connection);
return connection;
}
/**
* Destroy a connection
*/
async destroyConnection(connection) {
try {
if (this.options.connectionDestroyer) {
await this.options.connectionDestroyer(connection.connection);
}
this.metrics.connectionsDestroyed++;
this.eventEmitter.emit('destroy', connection);
}
catch (err) {
if (this.options.onConnectionError) {
this.options.onConnectionError(err, connection.connection);
}
}
}
/**
* Get current pool statistics
*/
getStats() {
return {
available: this.availableConnections.length,
active: this.activeConnections.size,
waiting: this.waitQueue.size,
total: this.availableConnections.length + this.activeConnections.size,
...this.metrics,
};
}
/**
* Subscribe to pool events
*/
on(event, listener) {
this.addEventListener(this.eventEmitter, event, listener);
}
/**
* Close the pool and cleanup resources
*/
async onCleanup() {
this.isClosing = true;
// Clear the wait queue
while (!this.waitQueue.isEmpty()) {
const request = this.waitQueue.extract();
if (request) {
this.clearTimeout(request.timeoutHandle);
request.reject(new Error('Connection pool is closing'));
}
}
// Wait for active connections to be released (with timeout)
const timeout = 30000;
const startTime = Date.now();
while (this.activeConnections.size > 0 && Date.now() - startTime < timeout) {
await new Promise(resolve => {
const timer = setTimeout(resolve, 100);
if (typeof timer.unref === 'function') {
timer.unref();
}
});
}
// Destroy all connections
const allConnections = [
...this.availableConnections,
...this.activeConnections.values(),
];
await Promise.all(allConnections.map(conn => this.destroyConnection(conn)));
this.availableConnections.length = 0;
this.activeConnections.clear();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW5oYW5jZWQtY29ubmVjdGlvbi1wb29sLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY29yZS91dGlscy9lbmhhbmNlZC1jb25uZWN0aW9uLXBvb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDOUQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBNkN0Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxzQkFBMEIsU0FBUSxrQkFBa0I7SUF1Qi9ELFlBQVksT0FBa0M7UUFDNUMsS0FBSyxFQUFFLENBQUM7UUF0Qk8seUJBQW9CLEdBQTJCLEVBQUUsQ0FBQztRQUNsRCxzQkFBaUIsR0FBc0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUVqRSxVQUFLLEdBQUcsSUFBSSxVQUFVLEVBQUUsQ0FBQztRQUN6QixpQkFBWSxHQUFHLElBQUksWUFBWSxFQUFFLENBQUM7UUFFM0Msd0JBQW1CLEdBQUcsQ0FBQyxDQUFDO1FBQ3hCLHFCQUFnQixHQUFHLENBQUMsQ0FBQztRQUNyQixjQUFTLEdBQUcsS0FBSyxDQUFDO1FBRTFCLFVBQVU7UUFDRixZQUFPLEdBQUc7WUFDaEIsa0JBQWtCLEVBQUUsQ0FBQztZQUNyQixvQkFBb0IsRUFBRSxDQUFDO1lBQ3ZCLG1CQUFtQixFQUFFLENBQUM7WUFDdEIsbUJBQW1CLEVBQUUsQ0FBQztZQUN0QixlQUFlLEVBQUUsQ0FBQztZQUNsQixrQkFBa0IsRUFBRSxDQUFDO1lBQ3JCLGtCQUFrQixFQUFFLENBQUM7U0FDdEIsQ0FBQztRQUtBLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixPQUFPLEVBQUUsQ0FBQztZQUNWLE9BQU8sRUFBRSxFQUFFO1lBQ1gs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