@naman_deep_singh/communication-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -0
- package/dist/cjs/abstract/BaseCircuitBreaker.js +253 -0
- package/dist/cjs/abstract/BaseClient.js +298 -0
- package/dist/cjs/abstract/BaseCompressionManager.js +377 -0
- package/dist/cjs/abstract/BaseConnectionPool.js +543 -0
- package/dist/cjs/abstract/BaseInterceptor.js +235 -0
- package/dist/cjs/abstract/BaseLoadBalancer.js +269 -0
- package/dist/cjs/abstract/BaseProtocol.js +269 -0
- package/dist/cjs/abstract/BaseRetryStrategy.js +255 -0
- package/dist/cjs/abstract/BaseSerializer.js +341 -0
- package/dist/cjs/abstract/BaseServiceDiscoverer.js +254 -0
- package/dist/cjs/abstract/BaseTimeoutManager.js +295 -0
- package/dist/cjs/abstract/index.js +25 -0
- package/dist/cjs/errors/CircuitBreakerError.js +16 -0
- package/dist/cjs/errors/CommunicationError.js +15 -0
- package/dist/cjs/errors/ConnectionError.js +15 -0
- package/dist/cjs/errors/DiscoveryError.js +15 -0
- package/dist/cjs/errors/LoadBalancerError.js +15 -0
- package/dist/cjs/errors/ProtocolError.js +15 -0
- package/dist/cjs/errors/RetryError.js +16 -0
- package/dist/cjs/errors/SerializationError.js +15 -0
- package/dist/cjs/errors/ServiceUnavailableError.js +15 -0
- package/dist/cjs/errors/TimeoutError.js +16 -0
- package/dist/cjs/errors/communicationErrorCodes.js +35 -0
- package/dist/cjs/errors/index.js +31 -0
- package/dist/cjs/index.js +38 -0
- package/dist/cjs/interfaces/CircuitBreaker.interface.js +6 -0
- package/dist/cjs/interfaces/Client.interface.js +6 -0
- package/dist/cjs/interfaces/Compression.interface.js +6 -0
- package/dist/cjs/interfaces/ConnectionPool.interface.js +6 -0
- package/dist/cjs/interfaces/Interceptor.interface.js +6 -0
- package/dist/cjs/interfaces/LoadBalancer.interface.js +2 -0
- package/dist/cjs/interfaces/Protocol.interface.js +6 -0
- package/dist/cjs/interfaces/RetryStrategy.interface.js +6 -0
- package/dist/cjs/interfaces/Serializer.interface.js +2 -0
- package/dist/cjs/interfaces/ServiceDiscovery.interface.js +6 -0
- package/dist/cjs/interfaces/Timeout.interface.js +6 -0
- package/dist/cjs/interfaces/index.js +6 -0
- package/dist/cjs/types/config.js +6 -0
- package/dist/cjs/types/events.js +6 -0
- package/dist/cjs/types/index.js +6 -0
- package/dist/cjs/types/request.js +6 -0
- package/dist/cjs/types/response.js +6 -0
- package/dist/cjs/types/service.js +6 -0
- package/dist/cjs/utils.js +200 -0
- package/dist/esm/abstract/BaseCircuitBreaker.js +249 -0
- package/dist/esm/abstract/BaseClient.js +294 -0
- package/dist/esm/abstract/BaseCompressionManager.js +373 -0
- package/dist/esm/abstract/BaseConnectionPool.js +539 -0
- package/dist/esm/abstract/BaseInterceptor.js +231 -0
- package/dist/esm/abstract/BaseLoadBalancer.js +265 -0
- package/dist/esm/abstract/BaseProtocol.js +265 -0
- package/dist/esm/abstract/BaseRetryStrategy.js +251 -0
- package/dist/esm/abstract/BaseSerializer.js +337 -0
- package/dist/esm/abstract/BaseServiceDiscoverer.js +250 -0
- package/dist/esm/abstract/BaseTimeoutManager.js +291 -0
- package/dist/esm/abstract/index.js +11 -0
- package/dist/esm/errors/CircuitBreakerError.js +12 -0
- package/dist/esm/errors/CommunicationError.js +11 -0
- package/dist/esm/errors/ConnectionError.js +11 -0
- package/dist/esm/errors/DiscoveryError.js +11 -0
- package/dist/esm/errors/LoadBalancerError.js +11 -0
- package/dist/esm/errors/ProtocolError.js +11 -0
- package/dist/esm/errors/RetryError.js +12 -0
- package/dist/esm/errors/SerializationError.js +11 -0
- package/dist/esm/errors/ServiceUnavailableError.js +11 -0
- package/dist/esm/errors/TimeoutError.js +12 -0
- package/dist/esm/errors/communicationErrorCodes.js +32 -0
- package/dist/esm/errors/index.js +17 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/interfaces/CircuitBreaker.interface.js +5 -0
- package/dist/esm/interfaces/Client.interface.js +5 -0
- package/dist/esm/interfaces/Compression.interface.js +5 -0
- package/dist/esm/interfaces/ConnectionPool.interface.js +5 -0
- package/dist/esm/interfaces/Interceptor.interface.js +5 -0
- package/dist/esm/interfaces/LoadBalancer.interface.js +1 -0
- package/dist/esm/interfaces/Protocol.interface.js +5 -0
- package/dist/esm/interfaces/RetryStrategy.interface.js +5 -0
- package/dist/esm/interfaces/Serializer.interface.js +1 -0
- package/dist/esm/interfaces/ServiceDiscovery.interface.js +5 -0
- package/dist/esm/interfaces/Timeout.interface.js +5 -0
- package/dist/esm/interfaces/index.js +5 -0
- package/dist/esm/types/config.js +5 -0
- package/dist/esm/types/events.js +5 -0
- package/dist/esm/types/index.js +5 -0
- package/dist/esm/types/request.js +5 -0
- package/dist/esm/types/response.js +5 -0
- package/dist/esm/types/service.js +5 -0
- package/dist/esm/utils.js +193 -0
- package/dist/types/abstract/BaseCircuitBreaker.d.ts +167 -0
- package/dist/types/abstract/BaseClient.d.ts +197 -0
- package/dist/types/abstract/BaseCompressionManager.d.ts +180 -0
- package/dist/types/abstract/BaseConnectionPool.d.ts +210 -0
- package/dist/types/abstract/BaseInterceptor.d.ts +150 -0
- package/dist/types/abstract/BaseLoadBalancer.d.ts +167 -0
- package/dist/types/abstract/BaseProtocol.d.ts +163 -0
- package/dist/types/abstract/BaseRetryStrategy.d.ts +130 -0
- package/dist/types/abstract/BaseSerializer.d.ts +181 -0
- package/dist/types/abstract/BaseServiceDiscoverer.d.ts +161 -0
- package/dist/types/abstract/BaseTimeoutManager.d.ts +145 -0
- package/dist/types/abstract/index.d.ts +11 -0
- package/dist/types/errors/CircuitBreakerError.d.ts +8 -0
- package/dist/types/errors/CommunicationError.d.ts +10 -0
- package/dist/types/errors/ConnectionError.d.ts +9 -0
- package/dist/types/errors/DiscoveryError.d.ts +9 -0
- package/dist/types/errors/LoadBalancerError.d.ts +9 -0
- package/dist/types/errors/ProtocolError.d.ts +9 -0
- package/dist/types/errors/RetryError.d.ts +11 -0
- package/dist/types/errors/SerializationError.d.ts +9 -0
- package/dist/types/errors/ServiceUnavailableError.d.ts +12 -0
- package/dist/types/errors/TimeoutError.d.ts +11 -0
- package/dist/types/errors/communicationErrorCodes.d.ts +27 -0
- package/dist/types/errors/index.d.ts +11 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/interfaces/CircuitBreaker.interface.d.ts +150 -0
- package/dist/types/interfaces/Client.interface.d.ts +153 -0
- package/dist/types/interfaces/Compression.interface.d.ts +190 -0
- package/dist/types/interfaces/ConnectionPool.interface.d.ts +191 -0
- package/dist/types/interfaces/Interceptor.interface.d.ts +220 -0
- package/dist/types/interfaces/LoadBalancer.interface.d.ts +153 -0
- package/dist/types/interfaces/Protocol.interface.d.ts +117 -0
- package/dist/types/interfaces/RetryStrategy.interface.d.ts +160 -0
- package/dist/types/interfaces/Serializer.interface.d.ts +176 -0
- package/dist/types/interfaces/ServiceDiscovery.interface.d.ts +189 -0
- package/dist/types/interfaces/Timeout.interface.d.ts +135 -0
- package/dist/types/interfaces/index.d.ts +15 -0
- package/dist/types/types/config.d.ts +540 -0
- package/dist/types/types/events.d.ts +204 -0
- package/dist/types/types/index.d.ts +9 -0
- package/dist/types/types/request.d.ts +143 -0
- package/dist/types/types/response.d.ts +155 -0
- package/dist/types/types/service.d.ts +279 -0
- package/dist/types/utils.d.ts +179 -0
- package/package.json +88 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base connection pool implementation
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { CommunicationError } from '../errors/CommunicationError.js';
|
|
6
|
+
import { COMMUNICATION_ERROR_CODES } from '../errors/communicationErrorCodes.js';
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base connection pool implementation
|
|
9
|
+
* Provides common functionality for connection pooling
|
|
10
|
+
*/
|
|
11
|
+
export class BaseConnectionPool {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new base connection pool instance
|
|
14
|
+
* @param name Pool name
|
|
15
|
+
* @param config Pool configuration
|
|
16
|
+
* @param createConnection Connection factory function
|
|
17
|
+
* @param validateConnection Connection validation function
|
|
18
|
+
*/
|
|
19
|
+
constructor(name, config, createConnection, validateConnection) {
|
|
20
|
+
/** Is pool healthy? */
|
|
21
|
+
this.isHealthy = true;
|
|
22
|
+
/** Total connections in pool */
|
|
23
|
+
this.totalConnections = 0;
|
|
24
|
+
/** Active connections */
|
|
25
|
+
this.activeConnections = 0;
|
|
26
|
+
/** Idle connections */
|
|
27
|
+
this.idleConnections = 0;
|
|
28
|
+
/** Waiting clients */
|
|
29
|
+
this.waitingClients = 0;
|
|
30
|
+
/** Connection pool */
|
|
31
|
+
this.pool = new Set();
|
|
32
|
+
/** Active connections set */
|
|
33
|
+
this.activeConnectionsSet = new Set();
|
|
34
|
+
/** Idle connections queue */
|
|
35
|
+
this.idleConnectionsQueue = [];
|
|
36
|
+
/** Waiting clients queue */
|
|
37
|
+
this.waitingQueue = [];
|
|
38
|
+
/** Pool statistics */
|
|
39
|
+
this.stats = {
|
|
40
|
+
totalAcquisitions: 0,
|
|
41
|
+
totalReleases: 0,
|
|
42
|
+
totalDestructions: 0,
|
|
43
|
+
totalErrors: 0,
|
|
44
|
+
totalAcquisitionTime: 0,
|
|
45
|
+
maxAcquisitionTime: 0,
|
|
46
|
+
connectionStats: new Map(),
|
|
47
|
+
};
|
|
48
|
+
this.name = name;
|
|
49
|
+
this.config = { ...config };
|
|
50
|
+
this.createConnectionFn = createConnection;
|
|
51
|
+
this.validateConnectionFn = validateConnection || (async (conn) => conn.isHealthy());
|
|
52
|
+
this.initialize();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize connection pool
|
|
56
|
+
*/
|
|
57
|
+
async initialize() {
|
|
58
|
+
// Set up cleanup interval
|
|
59
|
+
if (this.config.validationInterval) {
|
|
60
|
+
this.cleanupInterval = setInterval(() => this.cleanupIdleConnections(), this.config.validationInterval);
|
|
61
|
+
}
|
|
62
|
+
// Set up health check interval
|
|
63
|
+
if (this.config.validationInterval) {
|
|
64
|
+
this.healthCheckInterval = setInterval(() => this.checkPoolHealth(), this.config.validationInterval);
|
|
65
|
+
}
|
|
66
|
+
// Warm up pool if configured
|
|
67
|
+
if (this.config.warmup && this.config.minConnections) {
|
|
68
|
+
await this.warmupPool();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Warm up the pool with minimum connections
|
|
73
|
+
*/
|
|
74
|
+
async warmupPool() {
|
|
75
|
+
const minConnections = this.config.minConnections || 0;
|
|
76
|
+
const connectionsToCreate = Math.max(0, minConnections - this.totalConnections);
|
|
77
|
+
for (let i = 0; i < connectionsToCreate; i++) {
|
|
78
|
+
try {
|
|
79
|
+
const connection = await this.createConnection();
|
|
80
|
+
this.idleConnectionsQueue.push(connection);
|
|
81
|
+
this.pool.add(connection);
|
|
82
|
+
this.totalConnections++;
|
|
83
|
+
this.idleConnections++;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Log error but continue
|
|
87
|
+
this.stats.totalErrors++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Acquire a connection from the pool
|
|
93
|
+
* @param timeout Acquisition timeout in milliseconds
|
|
94
|
+
* @returns Promise resolving to connection acquisition
|
|
95
|
+
* @throws {CommunicationError} If timeout or pool exhausted
|
|
96
|
+
*/
|
|
97
|
+
async acquire(timeout) {
|
|
98
|
+
const startTime = Date.now();
|
|
99
|
+
const acquireTimeout = timeout || this.config.acquireTimeout || 10000;
|
|
100
|
+
// Check if we can create a new connection
|
|
101
|
+
if (this.activeConnections < (this.config.maxConnections || 10)) {
|
|
102
|
+
const connection = await this.getOrCreateConnection();
|
|
103
|
+
if (connection) {
|
|
104
|
+
const waitTime = Date.now() - startTime;
|
|
105
|
+
this.recordAcquisition(startTime, connection);
|
|
106
|
+
return this.createAcquisitionResult(connection, waitTime);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// If we can't create a new connection, wait for one to become available
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
this.removeWaitingClient(reject, startTime);
|
|
113
|
+
reject(new CommunicationError(COMMUNICATION_ERROR_CODES.CONNECTION_TIMEOUT, 503, {
|
|
114
|
+
message: `Connection acquisition timeout after ${acquireTimeout}ms`,
|
|
115
|
+
pool: this.name,
|
|
116
|
+
waitingClients: this.waitingClients,
|
|
117
|
+
activeConnections: this.activeConnections,
|
|
118
|
+
}));
|
|
119
|
+
}, acquireTimeout);
|
|
120
|
+
this.waitingQueue.push({
|
|
121
|
+
resolve: (acquisition) => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
resolve(acquisition);
|
|
124
|
+
},
|
|
125
|
+
reject: (error) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
reject(error);
|
|
128
|
+
},
|
|
129
|
+
timeout: timer,
|
|
130
|
+
startTime,
|
|
131
|
+
});
|
|
132
|
+
this.waitingClients++;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get or create a connection
|
|
137
|
+
* @returns Connection or null if cannot create
|
|
138
|
+
*/
|
|
139
|
+
async getOrCreateConnection() {
|
|
140
|
+
// Try to get from idle queue
|
|
141
|
+
while (this.idleConnectionsQueue.length > 0) {
|
|
142
|
+
const connection = this.idleConnectionsQueue.shift();
|
|
143
|
+
if (await this.validateConnection(connection)) {
|
|
144
|
+
return connection;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
await this.destroyConnection(connection);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Create new connection if under limit
|
|
151
|
+
if (this.totalConnections < (this.config.maxConnections || 10)) {
|
|
152
|
+
try {
|
|
153
|
+
const connection = await this.createConnection();
|
|
154
|
+
this.pool.add(connection);
|
|
155
|
+
this.totalConnections++;
|
|
156
|
+
return connection;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.stats.totalErrors++;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validate a connection
|
|
167
|
+
* @param connection Connection to validate
|
|
168
|
+
* @returns True if connection is valid
|
|
169
|
+
*/
|
|
170
|
+
async validateConnection(connection) {
|
|
171
|
+
try {
|
|
172
|
+
const isValid = await this.validateConnectionFn(connection);
|
|
173
|
+
if (!isValid) {
|
|
174
|
+
await this.destroyConnection(connection);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return isValid;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
await this.destroyConnection(connection);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Release a connection back to the pool
|
|
186
|
+
* @param connection Connection to release
|
|
187
|
+
*/
|
|
188
|
+
async release(connection) {
|
|
189
|
+
// Remove from active connections
|
|
190
|
+
this.activeConnectionsSet.delete(connection);
|
|
191
|
+
this.activeConnections--;
|
|
192
|
+
// Validate connection before returning to pool
|
|
193
|
+
if (await this.validateConnection(connection)) {
|
|
194
|
+
// Reset connection state
|
|
195
|
+
await connection.reset();
|
|
196
|
+
// Add to idle queue
|
|
197
|
+
this.idleConnectionsQueue.push(connection);
|
|
198
|
+
this.idleConnections++;
|
|
199
|
+
// Update connection stats
|
|
200
|
+
this.updateConnectionStats(connection, true);
|
|
201
|
+
// Notify waiting clients
|
|
202
|
+
this.notifyWaitingClients();
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Destroy invalid connection
|
|
206
|
+
await this.destroyConnection(connection);
|
|
207
|
+
}
|
|
208
|
+
this.stats.totalReleases++;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Destroy a connection (remove from pool)
|
|
212
|
+
* @param connection Connection to destroy
|
|
213
|
+
*/
|
|
214
|
+
async destroy(connection) {
|
|
215
|
+
await this.destroyConnection(connection);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Destroy a connection (internal)
|
|
219
|
+
* @param connection Connection to destroy
|
|
220
|
+
*/
|
|
221
|
+
async destroyConnection(connection) {
|
|
222
|
+
try {
|
|
223
|
+
await connection.close();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
// Ignore close errors
|
|
227
|
+
}
|
|
228
|
+
// Remove from all collections
|
|
229
|
+
this.pool.delete(connection);
|
|
230
|
+
this.activeConnectionsSet.delete(connection);
|
|
231
|
+
const idleIndex = this.idleConnectionsQueue.indexOf(connection);
|
|
232
|
+
if (idleIndex !== -1) {
|
|
233
|
+
this.idleConnectionsQueue.splice(idleIndex, 1);
|
|
234
|
+
this.idleConnections--;
|
|
235
|
+
}
|
|
236
|
+
this.totalConnections--;
|
|
237
|
+
this.stats.totalDestructions++;
|
|
238
|
+
// Remove from stats
|
|
239
|
+
this.stats.connectionStats.delete(connection.id);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Execute a function with a connection from the pool
|
|
243
|
+
* @param fn Function to execute with connection
|
|
244
|
+
* @param timeout Operation timeout in milliseconds
|
|
245
|
+
* @returns Promise resolving to function result
|
|
246
|
+
*/
|
|
247
|
+
async withConnection(fn, timeout) {
|
|
248
|
+
const acquisition = await this.acquire(timeout);
|
|
249
|
+
try {
|
|
250
|
+
const result = await fn(acquisition.connection);
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
await this.release(acquisition.connection);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Record connection acquisition
|
|
259
|
+
* @param startTime Acquisition start time
|
|
260
|
+
* @param connection Acquired connection
|
|
261
|
+
*/
|
|
262
|
+
recordAcquisition(startTime, connection) {
|
|
263
|
+
const waitTime = Date.now() - startTime;
|
|
264
|
+
// Update connection stats
|
|
265
|
+
const connStats = this.stats.connectionStats.get(connection.id) || {
|
|
266
|
+
createdAt: connection.createdAt,
|
|
267
|
+
lastUsedAt: Date.now(),
|
|
268
|
+
usageCount: 0,
|
|
269
|
+
totalUsageTime: 0,
|
|
270
|
+
isHealthy: connection.isHealthy(),
|
|
271
|
+
};
|
|
272
|
+
connStats.usageCount++;
|
|
273
|
+
connStats.lastUsedAt = Date.now();
|
|
274
|
+
this.stats.connectionStats.set(connection.id, connStats);
|
|
275
|
+
// Update pool stats
|
|
276
|
+
this.stats.totalAcquisitions++;
|
|
277
|
+
this.stats.totalAcquisitionTime += waitTime;
|
|
278
|
+
this.stats.maxAcquisitionTime = Math.max(this.stats.maxAcquisitionTime, waitTime);
|
|
279
|
+
// Update pool state
|
|
280
|
+
const idleIndex = this.idleConnectionsQueue.indexOf(connection);
|
|
281
|
+
if (idleIndex !== -1) {
|
|
282
|
+
this.idleConnectionsQueue.splice(idleIndex, 1);
|
|
283
|
+
this.idleConnections--;
|
|
284
|
+
}
|
|
285
|
+
this.activeConnectionsSet.add(connection);
|
|
286
|
+
this.activeConnections++;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Create acquisition result
|
|
290
|
+
* @param connection Acquired connection
|
|
291
|
+
* @param waitTime Wait time in milliseconds
|
|
292
|
+
* @returns Connection acquisition result
|
|
293
|
+
*/
|
|
294
|
+
createAcquisitionResult(connection, waitTime) {
|
|
295
|
+
return {
|
|
296
|
+
connection,
|
|
297
|
+
timestamp: Date.now(),
|
|
298
|
+
waitTime,
|
|
299
|
+
poolStats: {
|
|
300
|
+
totalConnections: this.totalConnections,
|
|
301
|
+
activeConnections: this.activeConnections,
|
|
302
|
+
idleConnections: this.idleConnections,
|
|
303
|
+
waitingClients: this.waitingClients,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Update connection statistics
|
|
309
|
+
* @param connection Connection
|
|
310
|
+
* @param released Whether connection was released
|
|
311
|
+
*/
|
|
312
|
+
updateConnectionStats(connection, released) {
|
|
313
|
+
const connStats = this.stats.connectionStats.get(connection.id);
|
|
314
|
+
if (connStats && released) {
|
|
315
|
+
const usageTime = Date.now() - connStats.lastUsedAt;
|
|
316
|
+
connStats.totalUsageTime += usageTime;
|
|
317
|
+
connStats.isHealthy = connection.isHealthy();
|
|
318
|
+
this.stats.connectionStats.set(connection.id, connStats);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Remove waiting client from queue
|
|
323
|
+
* @param reject Reject function
|
|
324
|
+
* @param startTime Start time
|
|
325
|
+
*/
|
|
326
|
+
removeWaitingClient(reject, startTime) {
|
|
327
|
+
const index = this.waitingQueue.findIndex(client => client.reject === reject);
|
|
328
|
+
if (index !== -1) {
|
|
329
|
+
this.waitingQueue.splice(index, 1);
|
|
330
|
+
this.waitingClients--;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Notify waiting clients
|
|
335
|
+
*/
|
|
336
|
+
async notifyWaitingClients() {
|
|
337
|
+
while (this.waitingQueue.length > 0 && this.idleConnectionsQueue.length > 0) {
|
|
338
|
+
const connection = this.idleConnectionsQueue.shift();
|
|
339
|
+
if (!connection)
|
|
340
|
+
break;
|
|
341
|
+
if (await this.validateConnection(connection)) {
|
|
342
|
+
const client = this.waitingQueue.shift();
|
|
343
|
+
if (!client) {
|
|
344
|
+
this.idleConnectionsQueue.unshift(connection);
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
this.waitingClients--;
|
|
348
|
+
clearTimeout(client.timeout);
|
|
349
|
+
const waitTime = Date.now() - client.startTime;
|
|
350
|
+
this.recordAcquisition(client.startTime, connection);
|
|
351
|
+
client.resolve(this.createAcquisitionResult(connection, waitTime));
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
await this.destroyConnection(connection);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Clean up idle connections
|
|
360
|
+
*/
|
|
361
|
+
async cleanupIdleConnections() {
|
|
362
|
+
const now = Date.now();
|
|
363
|
+
const maxIdleTime = this.config.idleTimeout || 60000;
|
|
364
|
+
for (let i = this.idleConnectionsQueue.length - 1; i >= 0; i--) {
|
|
365
|
+
const connection = this.idleConnectionsQueue[i];
|
|
366
|
+
const idleTime = now - connection.lastUsedAt;
|
|
367
|
+
// Check idle timeout
|
|
368
|
+
if (idleTime > maxIdleTime) {
|
|
369
|
+
this.idleConnectionsQueue.splice(i, 1);
|
|
370
|
+
this.idleConnections--;
|
|
371
|
+
await this.destroyConnection(connection);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
// Check max lifetime
|
|
375
|
+
if (this.config.maxLifetime) {
|
|
376
|
+
const age = now - connection.createdAt;
|
|
377
|
+
if (age > this.config.maxLifetime) {
|
|
378
|
+
this.idleConnectionsQueue.splice(i, 1);
|
|
379
|
+
this.idleConnections--;
|
|
380
|
+
await this.destroyConnection(connection);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Check pool health
|
|
387
|
+
*/
|
|
388
|
+
async checkPoolHealth() {
|
|
389
|
+
try {
|
|
390
|
+
// Validate all idle connections
|
|
391
|
+
for (const connection of this.idleConnectionsQueue) {
|
|
392
|
+
if (!(await this.validateConnection(connection))) {
|
|
393
|
+
const index = this.idleConnectionsQueue.indexOf(connection);
|
|
394
|
+
if (index !== -1) {
|
|
395
|
+
this.idleConnectionsQueue.splice(index, 1);
|
|
396
|
+
this.idleConnections--;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Check if pool meets minimum connections
|
|
401
|
+
if (this.config.minConnections && this.totalConnections < this.config.minConnections) {
|
|
402
|
+
await this.warmupPool();
|
|
403
|
+
}
|
|
404
|
+
this.isHealthy = true;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
this.isHealthy = false;
|
|
408
|
+
this.stats.totalErrors++;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Drain the pool (gracefully close all connections)
|
|
413
|
+
* @param force Force immediate drain
|
|
414
|
+
*/
|
|
415
|
+
async drain(force) {
|
|
416
|
+
// Stop accepting new requests
|
|
417
|
+
this.isHealthy = false;
|
|
418
|
+
// Clear intervals
|
|
419
|
+
if (this.cleanupInterval) {
|
|
420
|
+
clearInterval(this.cleanupInterval);
|
|
421
|
+
this.cleanupInterval = undefined;
|
|
422
|
+
}
|
|
423
|
+
if (this.healthCheckInterval) {
|
|
424
|
+
clearInterval(this.healthCheckInterval);
|
|
425
|
+
this.healthCheckInterval = undefined;
|
|
426
|
+
}
|
|
427
|
+
// Wait for active connections to finish (if not forced)
|
|
428
|
+
if (!force) {
|
|
429
|
+
const maxWaitTime = 30000; // 30 seconds max wait
|
|
430
|
+
const startTime = Date.now();
|
|
431
|
+
while (this.activeConnections > 0 && (Date.now() - startTime) < maxWaitTime) {
|
|
432
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Close all connections
|
|
436
|
+
const closePromises = Array.from(this.pool).map(conn => conn.close());
|
|
437
|
+
await Promise.allSettled(closePromises);
|
|
438
|
+
// Clear all collections
|
|
439
|
+
this.pool.clear();
|
|
440
|
+
this.activeConnectionsSet.clear();
|
|
441
|
+
this.idleConnectionsQueue.length = 0;
|
|
442
|
+
this.totalConnections = 0;
|
|
443
|
+
this.activeConnections = 0;
|
|
444
|
+
this.idleConnections = 0;
|
|
445
|
+
// Clear waiting queue
|
|
446
|
+
for (const client of this.waitingQueue) {
|
|
447
|
+
clearTimeout(client.timeout);
|
|
448
|
+
client.reject(new CommunicationError(COMMUNICATION_ERROR_CODES.CONNECTION_ERROR, 503, { message: 'Pool is draining' }));
|
|
449
|
+
}
|
|
450
|
+
this.waitingQueue.length = 0;
|
|
451
|
+
this.waitingClients = 0;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clear the pool (force close all connections)
|
|
455
|
+
*/
|
|
456
|
+
async clear() {
|
|
457
|
+
await this.drain(true);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Health check for the pool
|
|
461
|
+
*/
|
|
462
|
+
async healthCheck() {
|
|
463
|
+
const healthy = this.isHealthy && this.totalConnections > 0;
|
|
464
|
+
return {
|
|
465
|
+
healthy,
|
|
466
|
+
message: healthy ? 'Connection pool is healthy' : 'Connection pool is not healthy',
|
|
467
|
+
details: {
|
|
468
|
+
name: this.name,
|
|
469
|
+
totalConnections: this.totalConnections,
|
|
470
|
+
activeConnections: this.activeConnections,
|
|
471
|
+
idleConnections: this.idleConnections,
|
|
472
|
+
waitingClients: this.waitingClients,
|
|
473
|
+
config: this.config,
|
|
474
|
+
statistics: this.getStats(),
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get pool statistics
|
|
480
|
+
*/
|
|
481
|
+
getStats() {
|
|
482
|
+
const connectionStats = Array.from(this.stats.connectionStats.entries()).map(([id, stats]) => ({
|
|
483
|
+
id,
|
|
484
|
+
createdAt: stats.createdAt,
|
|
485
|
+
lastUsedAt: stats.lastUsedAt,
|
|
486
|
+
usageCount: stats.usageCount,
|
|
487
|
+
isHealthy: stats.isHealthy,
|
|
488
|
+
}));
|
|
489
|
+
const acquisitionTimeAverage = this.stats.totalAcquisitions > 0
|
|
490
|
+
? this.stats.totalAcquisitionTime / this.stats.totalAcquisitions
|
|
491
|
+
: 0;
|
|
492
|
+
const poolUtilization = this.config.maxConnections
|
|
493
|
+
? this.activeConnections / this.config.maxConnections
|
|
494
|
+
: 0;
|
|
495
|
+
return {
|
|
496
|
+
totalConnections: this.totalConnections,
|
|
497
|
+
activeConnections: this.activeConnections,
|
|
498
|
+
idleConnections: this.idleConnections,
|
|
499
|
+
waitingClients: this.waitingClients,
|
|
500
|
+
totalAcquisitions: this.stats.totalAcquisitions,
|
|
501
|
+
totalReleases: this.stats.totalReleases,
|
|
502
|
+
totalDestructions: this.stats.totalDestructions,
|
|
503
|
+
totalErrors: this.stats.totalErrors,
|
|
504
|
+
acquisitionTimeAverage,
|
|
505
|
+
acquisitionTimeMax: this.stats.maxAcquisitionTime,
|
|
506
|
+
poolUtilization,
|
|
507
|
+
connectionStats,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Reset pool statistics
|
|
512
|
+
*/
|
|
513
|
+
resetStats() {
|
|
514
|
+
this.stats = {
|
|
515
|
+
totalAcquisitions: 0,
|
|
516
|
+
totalReleases: 0,
|
|
517
|
+
totalDestructions: 0,
|
|
518
|
+
totalErrors: 0,
|
|
519
|
+
totalAcquisitionTime: 0,
|
|
520
|
+
maxAcquisitionTime: 0,
|
|
521
|
+
connectionStats: new Map(),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Update pool configuration
|
|
526
|
+
* @param config New configuration
|
|
527
|
+
*/
|
|
528
|
+
updateConfig(config) {
|
|
529
|
+
this.config = { ...this.config, ...config };
|
|
530
|
+
// Restart intervals if needed
|
|
531
|
+
if (this.cleanupInterval) {
|
|
532
|
+
clearInterval(this.cleanupInterval);
|
|
533
|
+
}
|
|
534
|
+
if (this.healthCheckInterval) {
|
|
535
|
+
clearInterval(this.healthCheckInterval);
|
|
536
|
+
}
|
|
537
|
+
this.initialize();
|
|
538
|
+
}
|
|
539
|
+
}
|