@tachybase/module-multi-app 1.6.0 → 1.6.2
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 +34 -34
- package/README.zh-CN.md +34 -34
- package/client.d.ts +1 -1
- package/client.js +1 -1
- package/dist/externalVersion.js +5 -5
- package/dist/locale/en-US.json +48 -48
- package/dist/locale/es-ES.json +9 -9
- package/dist/locale/ko_KR.json +11 -11
- package/dist/locale/pt-BR.json +9 -9
- package/dist/locale/zh-CN.json +58 -58
- package/dist/node_modules/mariadb/callback.js +43 -8
- package/dist/node_modules/mariadb/check-node.js +30 -0
- package/dist/node_modules/mariadb/lib/cluster-callback.js +84 -0
- package/dist/node_modules/mariadb/lib/cluster.js +446 -0
- package/dist/node_modules/mariadb/lib/cmd/batch-bulk.js +576 -177
- package/dist/node_modules/mariadb/lib/cmd/change-user.js +54 -44
- package/dist/node_modules/mariadb/lib/cmd/class/ok-packet.js +3 -2
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-cache-wrapper.js +46 -0
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-result-packet.js +141 -0
- package/dist/node_modules/mariadb/lib/cmd/class/prepare-wrapper.js +70 -0
- package/dist/node_modules/mariadb/lib/cmd/close-prepare.js +38 -0
- package/dist/node_modules/mariadb/lib/cmd/column-definition.js +145 -47
- package/dist/node_modules/mariadb/lib/cmd/command.js +41 -75
- package/dist/node_modules/mariadb/lib/cmd/decoder/binary-decoder.js +282 -0
- package/dist/node_modules/mariadb/lib/cmd/decoder/text-decoder.js +210 -0
- package/dist/node_modules/mariadb/lib/cmd/{common-binary-cmd.js → encoder/binary-encoder.js} +34 -77
- package/dist/node_modules/mariadb/lib/cmd/encoder/text-encoder.js +311 -0
- package/dist/node_modules/mariadb/lib/cmd/execute-stream.js +61 -0
- package/dist/node_modules/mariadb/lib/cmd/execute.js +338 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/caching-sha2-password-auth.js +25 -62
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/clear-password-auth.js +39 -6
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/ed25519-password-auth.js +48 -16
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/handshake.js +198 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/{initial-handshake.js → auth/initial-handshake.js} +10 -8
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/native-password-auth.js +22 -9
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/pam-password-auth.js +9 -4
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/parsec-auth.js +115 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/plugin-auth.js +12 -5
- package/dist/node_modules/mariadb/lib/cmd/handshake/auth/sha256-password-auth.js +44 -33
- package/dist/node_modules/mariadb/lib/cmd/handshake/authentication.js +335 -0
- package/dist/node_modules/mariadb/lib/cmd/handshake/client-capabilities.js +20 -19
- package/dist/node_modules/mariadb/lib/cmd/handshake/ssl-request.js +6 -3
- package/dist/node_modules/mariadb/lib/cmd/parser.js +861 -0
- package/dist/node_modules/mariadb/lib/cmd/ping.js +17 -18
- package/dist/node_modules/mariadb/lib/cmd/prepare.js +170 -0
- package/dist/node_modules/mariadb/lib/cmd/query.js +281 -144
- package/dist/node_modules/mariadb/lib/cmd/quit.js +9 -6
- package/dist/node_modules/mariadb/lib/cmd/reset.js +15 -19
- package/dist/node_modules/mariadb/lib/cmd/stream.js +21 -6
- package/dist/node_modules/mariadb/lib/config/cluster-options.js +23 -0
- package/dist/node_modules/mariadb/lib/config/connection-options.js +196 -132
- package/dist/node_modules/mariadb/lib/config/pool-options.js +27 -19
- package/dist/node_modules/mariadb/lib/connection-callback.js +492 -120
- package/dist/node_modules/mariadb/lib/connection-promise.js +372 -0
- package/dist/node_modules/mariadb/lib/connection.js +1739 -1016
- package/dist/node_modules/mariadb/lib/const/capabilities.js +36 -30
- package/dist/node_modules/mariadb/lib/const/collations.js +972 -36
- package/dist/node_modules/mariadb/lib/const/connection_status.js +3 -0
- package/dist/node_modules/mariadb/lib/const/error-code.js +35 -11
- package/dist/node_modules/mariadb/lib/const/field-detail.js +3 -0
- package/dist/node_modules/mariadb/lib/const/field-type.js +7 -4
- package/dist/node_modules/mariadb/lib/const/server-status.js +4 -1
- package/dist/node_modules/mariadb/lib/const/state-change.js +3 -0
- package/dist/node_modules/mariadb/lib/filtered-cluster-callback.js +136 -0
- package/dist/node_modules/mariadb/lib/filtered-cluster.js +118 -0
- package/dist/node_modules/mariadb/lib/io/compression-input-stream.js +14 -13
- package/dist/node_modules/mariadb/lib/io/compression-output-stream.js +21 -18
- package/dist/node_modules/mariadb/lib/io/packet-input-stream.js +75 -64
- package/dist/node_modules/mariadb/lib/io/packet-node-encoded.js +13 -9
- package/dist/node_modules/mariadb/lib/io/packet-node-iconv.js +12 -10
- package/dist/node_modules/mariadb/lib/io/packet-output-stream.js +402 -134
- package/dist/node_modules/mariadb/lib/io/packet.js +287 -202
- package/dist/node_modules/mariadb/lib/lru-prepare-cache.js +84 -0
- package/dist/node_modules/mariadb/lib/misc/connection-information.js +15 -32
- package/dist/node_modules/mariadb/lib/misc/errors.js +68 -25
- package/dist/node_modules/mariadb/lib/misc/parse.js +207 -711
- package/dist/node_modules/mariadb/lib/misc/utils.js +34 -62
- package/dist/node_modules/mariadb/lib/pool-callback.js +213 -174
- package/dist/node_modules/mariadb/lib/pool-promise.js +228 -94
- package/dist/node_modules/mariadb/lib/pool.js +951 -0
- package/dist/node_modules/mariadb/package.json +1 -1
- package/dist/node_modules/mariadb/promise.js +1 -34
- package/dist/node_modules/mariadb/types/callback.d.ts +207 -0
- package/dist/node_modules/mariadb/types/index.d.ts +94 -674
- package/dist/node_modules/mariadb/types/share.d.ts +804 -0
- package/dist/node_modules/qs/package.json +1 -1
- package/dist/server/actions/apps.js +2 -2
- package/dist/server/app-lifecycle.d.ts +1 -1
- package/dist/server/app-lifecycle.js +4 -4
- package/dist/server/models/application.d.ts +1 -1
- package/package.json +7 -7
- package/server.d.ts +2 -2
- package/server.js +1 -1
- package/dist/node_modules/mariadb/lib/cmd/batch-rewrite.js +0 -372
- package/dist/node_modules/mariadb/lib/cmd/common-text-cmd.js +0 -427
- package/dist/node_modules/mariadb/lib/cmd/handshake/client-handshake-response.js +0 -126
- package/dist/node_modules/mariadb/lib/cmd/handshake/handshake.js +0 -292
- package/dist/node_modules/mariadb/lib/cmd/resultset.js +0 -607
- package/dist/node_modules/mariadb/lib/config/pool-cluster-options.js +0 -19
- package/dist/node_modules/mariadb/lib/filtered-pool-cluster.js +0 -81
- package/dist/node_modules/mariadb/lib/io/bulk-packet.js +0 -590
- package/dist/node_modules/mariadb/lib/io/rewrite-packet.js +0 -481
- package/dist/node_modules/mariadb/lib/pool-base.js +0 -611
- package/dist/node_modules/mariadb/lib/pool-cluster-callback.js +0 -66
- package/dist/node_modules/mariadb/lib/pool-cluster.js +0 -407
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
2
|
+
// Copyright (c) 2015-2025 MariaDB Corporation Ab
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('events');
|
|
7
|
+
|
|
8
|
+
const Queue = require('denque');
|
|
9
|
+
const Errors = require('./misc/errors');
|
|
10
|
+
const Utils = require('./misc/utils');
|
|
11
|
+
const Connection = require('./connection');
|
|
12
|
+
|
|
13
|
+
class Pool extends EventEmitter {
|
|
14
|
+
opts;
|
|
15
|
+
#closed = false;
|
|
16
|
+
#connectionInCreation = false;
|
|
17
|
+
#errorCreatingConnection = null;
|
|
18
|
+
#idleConnections;
|
|
19
|
+
#activeConnections = {};
|
|
20
|
+
#requests = new Queue();
|
|
21
|
+
#unusedConnectionRemoverId;
|
|
22
|
+
#requestTimeoutId;
|
|
23
|
+
#connErrorNumber = 0;
|
|
24
|
+
#initialized = false;
|
|
25
|
+
_managePoolSizeTask;
|
|
26
|
+
_connectionCreationTask;
|
|
27
|
+
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super();
|
|
30
|
+
this.opts = options;
|
|
31
|
+
this.#idleConnections = new Queue(null, { capacity: this.opts.connectionLimit });
|
|
32
|
+
this.on('_idle', this._processNextPendingRequest);
|
|
33
|
+
this.on('validateSize', this._managePoolSize);
|
|
34
|
+
this._managePoolSize();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//*****************************************************************
|
|
38
|
+
// pool automatic handlers
|
|
39
|
+
//*****************************************************************
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Manages pool size by creating new connections when needed
|
|
43
|
+
*/
|
|
44
|
+
_managePoolSize() {
|
|
45
|
+
// Only create new connections if conditions are met and no creation is in progress
|
|
46
|
+
if (!this._shouldCreateMoreConnections() || this._managePoolSizeTask) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.#connectionInCreation = true;
|
|
51
|
+
|
|
52
|
+
const timeoutEnd = Date.now() + this.opts.initializationTimeout;
|
|
53
|
+
this._initiateConnectionCreation(timeoutEnd);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initiates connection creation with proper error handling
|
|
58
|
+
* @param {number} timeoutEnd - When the connection attempt should time out
|
|
59
|
+
*/
|
|
60
|
+
_initiateConnectionCreation(timeoutEnd) {
|
|
61
|
+
this._createPoolConnection(
|
|
62
|
+
// Success callback
|
|
63
|
+
() => this._onConnectionCreationSuccess(),
|
|
64
|
+
// Error callback
|
|
65
|
+
(err) => this._onConnectionCreationError(err, timeoutEnd),
|
|
66
|
+
timeoutEnd
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handles successful connection creation
|
|
72
|
+
*/
|
|
73
|
+
_onConnectionCreationSuccess() {
|
|
74
|
+
this.#initialized = true;
|
|
75
|
+
this.#errorCreatingConnection = null;
|
|
76
|
+
this.#connErrorNumber = 0;
|
|
77
|
+
this._connectionCreationTask = null;
|
|
78
|
+
|
|
79
|
+
// Check if we need more connections
|
|
80
|
+
if (this._shouldCreateMoreConnections()) {
|
|
81
|
+
this.emit('validateSize');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this._startConnectionReaping();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Handles errors during connection creation
|
|
89
|
+
* @param {Error} err - The error that occurred
|
|
90
|
+
* @param {number} timeoutEnd - When the connection attempt should time out
|
|
91
|
+
*/
|
|
92
|
+
_onConnectionCreationError(err, timeoutEnd) {
|
|
93
|
+
this.#connectionInCreation = false;
|
|
94
|
+
if (this.#closed) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (this.#errorCreatingConnection) err = this.#errorCreatingConnection;
|
|
98
|
+
|
|
99
|
+
// Format error message based on pool state
|
|
100
|
+
let error;
|
|
101
|
+
if (!this.#initialized) {
|
|
102
|
+
error = Errors.createError(
|
|
103
|
+
`Error during pool initialization`,
|
|
104
|
+
Errors.ER_POOL_NOT_INITIALIZED,
|
|
105
|
+
null,
|
|
106
|
+
null,
|
|
107
|
+
null,
|
|
108
|
+
false,
|
|
109
|
+
null,
|
|
110
|
+
null,
|
|
111
|
+
err
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
error = Errors.createError(
|
|
115
|
+
`Pool fails to create connection`,
|
|
116
|
+
Errors.ER_POOL_NO_CONNECTION,
|
|
117
|
+
null,
|
|
118
|
+
null,
|
|
119
|
+
null,
|
|
120
|
+
false,
|
|
121
|
+
null,
|
|
122
|
+
null,
|
|
123
|
+
err
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Schedule next attempt with exponential backoff
|
|
128
|
+
const backoffTime = Math.min(++this.#connErrorNumber * 200, 10000);
|
|
129
|
+
this._scheduleRetryWithBackoff(backoffTime);
|
|
130
|
+
|
|
131
|
+
this.emit('error', error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Schedules the next connection creation attempt with backoff
|
|
136
|
+
* @param {number} delay - Time to wait before next attempt
|
|
137
|
+
*/
|
|
138
|
+
_scheduleRetryWithBackoff(delay) {
|
|
139
|
+
if (this.#closed) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this._managePoolSizeTask = setTimeout(() => {
|
|
143
|
+
this._managePoolSizeTask = null;
|
|
144
|
+
if (!this.#requests.isEmpty()) {
|
|
145
|
+
this._managePoolSize();
|
|
146
|
+
}
|
|
147
|
+
}, delay);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Creates a new connection for the pool with proper error handling
|
|
152
|
+
* @param {Function} onSuccess - Success callback
|
|
153
|
+
* @param {Function} onError - Error callback
|
|
154
|
+
* @param {number} timeoutEnd - Timestamp when connection attempt should time out
|
|
155
|
+
*/
|
|
156
|
+
_createPoolConnection(onSuccess, onError, timeoutEnd) {
|
|
157
|
+
const minTimeout = timeoutEnd - Date.now();
|
|
158
|
+
const connectionOpts = Object.assign({}, this.opts.connOptions, {
|
|
159
|
+
connectTimeout: Math.max(1, Math.min(minTimeout, this.opts.connOptions.connectTimeout || Number.MAX_SAFE_INTEGER))
|
|
160
|
+
});
|
|
161
|
+
const conn = new Connection(connectionOpts);
|
|
162
|
+
this._connectionCreationTask = null;
|
|
163
|
+
// Use direct callback approach instead of Promise
|
|
164
|
+
conn
|
|
165
|
+
.connect()
|
|
166
|
+
.then((conn) => this._prepareNewConnection(conn, onSuccess, onError))
|
|
167
|
+
.catch((err) => this._handleConnectionCreationError(err, onSuccess, onError, timeoutEnd));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sets up a newly created connection for use in the pool
|
|
172
|
+
* @param {Connection} conn - The new connection
|
|
173
|
+
* @param {Function} onSuccess - Success callback
|
|
174
|
+
* @param {Function} onError - Error callback
|
|
175
|
+
*/
|
|
176
|
+
_prepareNewConnection(conn, onSuccess, onError) {
|
|
177
|
+
// Handle pool closed during connection creation
|
|
178
|
+
if (this.#closed) {
|
|
179
|
+
this._cleanupConnection(conn, 'pool_closed');
|
|
180
|
+
onError(
|
|
181
|
+
new Errors.createFatalError(
|
|
182
|
+
'Cannot create new connection to pool, pool closed',
|
|
183
|
+
Errors.ER_ADD_CONNECTION_CLOSED_POOL
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Initialize connection for pool use
|
|
190
|
+
conn.lastUse = Date.now();
|
|
191
|
+
|
|
192
|
+
// Setup connection for pool use
|
|
193
|
+
conn.forceEnd = conn.end;
|
|
194
|
+
conn.release = (callback) => this._handleRelease(conn, callback);
|
|
195
|
+
conn.end = conn.release;
|
|
196
|
+
|
|
197
|
+
// Override destroy method to handle pool cleanup
|
|
198
|
+
this._overrideConnectionMethods(conn);
|
|
199
|
+
|
|
200
|
+
// Setup error handler for connection failures
|
|
201
|
+
this._setupConnectionErrorHandler(conn);
|
|
202
|
+
|
|
203
|
+
// Add to idle connections and mark creation as complete
|
|
204
|
+
this.#idleConnections.push(conn);
|
|
205
|
+
this.#connectionInCreation = false;
|
|
206
|
+
|
|
207
|
+
// Emit events and call success callback
|
|
208
|
+
this.emit('_idle');
|
|
209
|
+
this.emit('connection', conn);
|
|
210
|
+
onSuccess(conn);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Overrides connection methods for pool integration
|
|
215
|
+
* @param {Connection} conn - The connection to modify
|
|
216
|
+
*/
|
|
217
|
+
_overrideConnectionMethods(conn) {
|
|
218
|
+
const nativeDestroy = conn.destroy.bind(conn);
|
|
219
|
+
const pool = this;
|
|
220
|
+
|
|
221
|
+
conn.destroy = function () {
|
|
222
|
+
pool._endLeak(conn);
|
|
223
|
+
delete pool.#activeConnections[conn.threadId];
|
|
224
|
+
nativeDestroy();
|
|
225
|
+
pool.emit('validateSize');
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Sets up error handler for a connection
|
|
231
|
+
* @param {Connection} conn - The connection to set up
|
|
232
|
+
*/
|
|
233
|
+
_setupConnectionErrorHandler(conn) {
|
|
234
|
+
const pool = this;
|
|
235
|
+
|
|
236
|
+
conn.once('error', () => {
|
|
237
|
+
// Clean up this connection
|
|
238
|
+
pool._endLeak(conn);
|
|
239
|
+
delete pool.#activeConnections[conn.threadId];
|
|
240
|
+
|
|
241
|
+
// Process idle connections
|
|
242
|
+
pool._processIdleConnectionsOnError(conn);
|
|
243
|
+
|
|
244
|
+
// Check if we need to create more connections
|
|
245
|
+
setImmediate(() => {
|
|
246
|
+
if (!pool.#requests.isEmpty()) {
|
|
247
|
+
pool._managePoolSize();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Processes idle connections when an error occurs
|
|
255
|
+
* @param {Connection} errorConn - The connection that had an error
|
|
256
|
+
*/
|
|
257
|
+
_processIdleConnectionsOnError(errorConn) {
|
|
258
|
+
let idx = 0;
|
|
259
|
+
while (idx < this.#idleConnections.length) {
|
|
260
|
+
const currConn = this.#idleConnections.peekAt(idx);
|
|
261
|
+
|
|
262
|
+
if (currConn === errorConn) {
|
|
263
|
+
this.#idleConnections.removeOne(idx);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Force validation on other connections
|
|
268
|
+
currConn.lastUse = Math.min(currConn.lastUse, Date.now() - this.opts.minDelayValidation);
|
|
269
|
+
idx++;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Handles errors during connection creation
|
|
275
|
+
* @param {Error} err - The error that occurred
|
|
276
|
+
* @param {Function} onSuccess - Success callback
|
|
277
|
+
* @param {Function} onError - Error callback
|
|
278
|
+
* @param {number} timeoutEnd - Timestamp when connection attempt should time out
|
|
279
|
+
*/
|
|
280
|
+
_handleConnectionCreationError(err, onSuccess, onError, timeoutEnd) {
|
|
281
|
+
// Handle connection creation errors
|
|
282
|
+
if (err instanceof AggregateError) {
|
|
283
|
+
err = err.errors[0];
|
|
284
|
+
}
|
|
285
|
+
if (!this.#errorCreatingConnection) this.#errorCreatingConnection = err;
|
|
286
|
+
// Determine if we should retry or fail
|
|
287
|
+
const isFatalError =
|
|
288
|
+
this.#closed || (err.errno && [1524, 1045, 1698].includes(err.errno)) || timeoutEnd < Date.now();
|
|
289
|
+
if (isFatalError) {
|
|
290
|
+
// Fatal error - call error callback with additional pool info
|
|
291
|
+
err.message = err.message + this._errorMsgAddon();
|
|
292
|
+
this._connectionCreationTask = null;
|
|
293
|
+
onError(err);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Retry connection after delay
|
|
298
|
+
this._connectionCreationTask = setTimeout(
|
|
299
|
+
() => this._createPoolConnection(onSuccess, onError, timeoutEnd),
|
|
300
|
+
Math.min(500, timeoutEnd - Date.now())
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Checks for timed-out requests and rejects them
|
|
306
|
+
*/
|
|
307
|
+
_checkRequestTimeouts() {
|
|
308
|
+
this.#requestTimeoutId = null;
|
|
309
|
+
const currentTime = Date.now();
|
|
310
|
+
|
|
311
|
+
while (this.#requests.length > 0) {
|
|
312
|
+
const request = this.#requests.peekFront();
|
|
313
|
+
|
|
314
|
+
if (this._hasRequestTimedOut(request, currentTime)) {
|
|
315
|
+
this._rejectTimedOutRequest(request, currentTime);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this._scheduleNextTimeoutCheck(request, currentTime);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Checks if a request has timed out
|
|
326
|
+
* @param {Request} request - The request to check
|
|
327
|
+
* @param {number} currentTime - Current timestamp
|
|
328
|
+
* @returns {boolean} - True if request has timed out
|
|
329
|
+
*/
|
|
330
|
+
_hasRequestTimedOut(request, currentTime) {
|
|
331
|
+
return request.timeout <= currentTime;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Rejects a timed out request
|
|
336
|
+
* @param {Request} request - The request to reject
|
|
337
|
+
* @param {number} currentTime - Current timestamp
|
|
338
|
+
*/
|
|
339
|
+
_rejectTimedOutRequest(request, currentTime) {
|
|
340
|
+
this.#requests.shift();
|
|
341
|
+
|
|
342
|
+
// Determine the cause of the timeout
|
|
343
|
+
const timeoutCause = this.activeConnections() === 0 ? this.#errorCreatingConnection : null;
|
|
344
|
+
const waitTime = Math.abs(currentTime - (request.timeout - this.opts.acquireTimeout));
|
|
345
|
+
|
|
346
|
+
// Create appropriate error message with pool state information
|
|
347
|
+
const timeoutError = Errors.createError(
|
|
348
|
+
`pool timeout: failed to retrieve a connection from pool after ${waitTime}ms${this._errorMsgAddon()}`,
|
|
349
|
+
Errors.ER_GET_CONNECTION_TIMEOUT,
|
|
350
|
+
null,
|
|
351
|
+
'HY000',
|
|
352
|
+
null,
|
|
353
|
+
false,
|
|
354
|
+
request.stack,
|
|
355
|
+
null,
|
|
356
|
+
timeoutCause
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
request.reject(timeoutError);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Schedules the next timeout check
|
|
364
|
+
* @param {Request} request - The next request in queue
|
|
365
|
+
* @param {number} currentTime - Current timestamp
|
|
366
|
+
*/
|
|
367
|
+
_scheduleNextTimeoutCheck(request, currentTime) {
|
|
368
|
+
const timeUntilNextTimeout = request.timeout - currentTime;
|
|
369
|
+
this.#requestTimeoutId = setTimeout(() => this._checkRequestTimeouts(), timeUntilNextTimeout);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
_destroy(conn) {
|
|
373
|
+
this._endLeak(conn);
|
|
374
|
+
delete this.#activeConnections[conn.threadId];
|
|
375
|
+
conn.lastUse = Date.now();
|
|
376
|
+
conn.forceEnd(
|
|
377
|
+
null,
|
|
378
|
+
() => {},
|
|
379
|
+
() => {}
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
if (this.totalConnections() === 0) {
|
|
383
|
+
this._stopConnectionReaping();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.emit('validateSize');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
release(conn) {
|
|
390
|
+
if (!this.#activeConnections[conn.threadId]) {
|
|
391
|
+
return; // Already released
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this._endLeak(conn);
|
|
395
|
+
this.#activeConnections[conn.threadId] = null;
|
|
396
|
+
conn.lastUse = Date.now();
|
|
397
|
+
|
|
398
|
+
if (this.#closed) {
|
|
399
|
+
this._cleanupConnection(conn, 'pool_closed');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Only basic validation here - full validation happens when acquiring
|
|
404
|
+
if (conn.isValid()) {
|
|
405
|
+
this.emit('release', conn);
|
|
406
|
+
this.#idleConnections.push(conn);
|
|
407
|
+
process.nextTick(this.emit.bind(this, '_idle'));
|
|
408
|
+
} else {
|
|
409
|
+
this._cleanupConnection(conn, 'validation_failed');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
_endLeak(conn) {
|
|
414
|
+
if (conn.leakProcess) {
|
|
415
|
+
clearTimeout(conn.leakProcess);
|
|
416
|
+
conn.leakProcess = null;
|
|
417
|
+
if (conn.leaked) {
|
|
418
|
+
conn.opts.logger.warning(
|
|
419
|
+
`Previous possible leak connection with thread ${conn.info.threadId} was returned to pool`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Permit to remove idle connection if unused for some time.
|
|
427
|
+
*/
|
|
428
|
+
_startConnectionReaping() {
|
|
429
|
+
if (!this.#unusedConnectionRemoverId && this.opts.idleTimeout > 0) {
|
|
430
|
+
this.#unusedConnectionRemoverId = setInterval(this._removeIdleConnections.bind(this), 500);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
_stopConnectionReaping() {
|
|
435
|
+
if (this.#unusedConnectionRemoverId && this.totalConnections() === 0) {
|
|
436
|
+
clearInterval(this.#unusedConnectionRemoverId);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Removes idle connections that have been unused for too long
|
|
442
|
+
*/
|
|
443
|
+
_removeIdleConnections() {
|
|
444
|
+
const idleTimeRemoval = Date.now() - this.opts.idleTimeout * 1000;
|
|
445
|
+
let maxRemoval = Math.max(0, this.#idleConnections.length - this.opts.minimumIdle);
|
|
446
|
+
|
|
447
|
+
while (maxRemoval > 0) {
|
|
448
|
+
const conn = this.#idleConnections.peek();
|
|
449
|
+
maxRemoval--;
|
|
450
|
+
|
|
451
|
+
if (conn && conn.lastUse < idleTimeRemoval) {
|
|
452
|
+
this.#idleConnections.shift();
|
|
453
|
+
conn.forceEnd(
|
|
454
|
+
null,
|
|
455
|
+
() => {},
|
|
456
|
+
() => {}
|
|
457
|
+
);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (this.totalConnections() === 0) {
|
|
464
|
+
this._stopConnectionReaping();
|
|
465
|
+
}
|
|
466
|
+
this.emit('validateSize');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
_shouldCreateMoreConnections() {
|
|
470
|
+
return (
|
|
471
|
+
!this.#connectionInCreation &&
|
|
472
|
+
this.#idleConnections.length < this.opts.minimumIdle &&
|
|
473
|
+
this.totalConnections() < this.opts.connectionLimit &&
|
|
474
|
+
!this.#closed
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Processes the next request in the queue if connections are available
|
|
480
|
+
*/
|
|
481
|
+
_processNextPendingRequest() {
|
|
482
|
+
clearTimeout(this.#requestTimeoutId);
|
|
483
|
+
this.#requestTimeoutId = null;
|
|
484
|
+
|
|
485
|
+
const request = this.#requests.shift();
|
|
486
|
+
if (!request) return;
|
|
487
|
+
|
|
488
|
+
const conn = this.#idleConnections.shift();
|
|
489
|
+
if (conn) {
|
|
490
|
+
if (this.opts.leakDetectionTimeout > 0) {
|
|
491
|
+
this._startLeakDetection(conn);
|
|
492
|
+
}
|
|
493
|
+
this.#activeConnections[conn.threadId] = conn;
|
|
494
|
+
this.emit('acquire', conn);
|
|
495
|
+
request.resolver(conn);
|
|
496
|
+
} else {
|
|
497
|
+
this.#requests.unshift(request);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this._checkRequestTimeouts();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
_hasIdleConnection() {
|
|
504
|
+
return !this.#idleConnections.isEmpty();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Acquires an idle connection from the pool
|
|
509
|
+
* @param {Function} callback - Callback function(err, conn)
|
|
510
|
+
*/
|
|
511
|
+
_acquireIdleConnection(callback) {
|
|
512
|
+
// Quick check if acquisition is possible
|
|
513
|
+
if (!this._hasIdleConnection() || this.#closed) {
|
|
514
|
+
callback(new Error('No idle connections available'));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
this._findValidIdleConnection(callback, false);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Search info object of an existing connection. to know server type and version.
|
|
523
|
+
* @returns information object if connection available.
|
|
524
|
+
*/
|
|
525
|
+
_searchInfo() {
|
|
526
|
+
let info = null;
|
|
527
|
+
let conn = this.#idleConnections.get(0);
|
|
528
|
+
|
|
529
|
+
if (!conn) {
|
|
530
|
+
for (const threadId in Object.keys(this.#activeConnections)) {
|
|
531
|
+
conn = this.#activeConnections[threadId];
|
|
532
|
+
if (!conn) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (conn) {
|
|
539
|
+
info = conn.info;
|
|
540
|
+
}
|
|
541
|
+
return info;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Recursively searches for a valid idle connection
|
|
546
|
+
* @param {Function} callback - Callback function(err, conn)
|
|
547
|
+
* @param {boolean} needPoolSizeCheck - Whether to check pool size after
|
|
548
|
+
*/
|
|
549
|
+
_findValidIdleConnection(callback, needPoolSizeCheck) {
|
|
550
|
+
if (this.#idleConnections.isEmpty()) {
|
|
551
|
+
// No more connections to check
|
|
552
|
+
if (needPoolSizeCheck) {
|
|
553
|
+
setImmediate(() => this.emit('validateSize'));
|
|
554
|
+
}
|
|
555
|
+
callback(new Error('No valid connections found'));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const conn = this.#idleConnections.shift();
|
|
560
|
+
this.#activeConnections[conn.threadId] = conn;
|
|
561
|
+
this._validateConnectionHealth(conn, (isValid) => {
|
|
562
|
+
if (isValid) {
|
|
563
|
+
if (this.opts.leakDetectionTimeout > 0) {
|
|
564
|
+
this._startLeakDetection(conn);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (needPoolSizeCheck) {
|
|
568
|
+
setImmediate(() => this.emit('validateSize'));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
callback(null, conn);
|
|
572
|
+
return;
|
|
573
|
+
} else {
|
|
574
|
+
delete this.#activeConnections[conn.threadId];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Connection failed validation, try next one
|
|
578
|
+
this._findValidIdleConnection(callback, true);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Validates if a connection is healthy and can be used
|
|
584
|
+
* @param {Connection} conn - The connection to validate
|
|
585
|
+
* @param {Function} callback - Callback function(isValid)
|
|
586
|
+
*/
|
|
587
|
+
_validateConnectionHealth(conn, callback) {
|
|
588
|
+
if (!conn) {
|
|
589
|
+
callback(false);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Skip validation if connection is already invalid or was recently used
|
|
594
|
+
const recentlyUsed = this.opts.minDelayValidation > 0 && Date.now() - conn.lastUse <= this.opts.minDelayValidation;
|
|
595
|
+
|
|
596
|
+
if (!conn.isValid() || recentlyUsed) {
|
|
597
|
+
callback(conn.isValid());
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Perform ping to verify connection is responsive
|
|
602
|
+
const pingOptions = { opts: { timeout: this.opts.pingTimeout } };
|
|
603
|
+
conn.ping(
|
|
604
|
+
pingOptions,
|
|
605
|
+
() => callback(true),
|
|
606
|
+
() => callback(false)
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
_leakedConnections() {
|
|
611
|
+
let counter = 0;
|
|
612
|
+
for (const connection of Object.values(this.#activeConnections)) {
|
|
613
|
+
if (connection && connection.leaked) counter++;
|
|
614
|
+
}
|
|
615
|
+
return counter;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
_errorMsgAddon() {
|
|
619
|
+
if (this.opts.leakDetectionTimeout > 0) {
|
|
620
|
+
return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} leak=${this._leakedConnections()} limit=${
|
|
621
|
+
this.opts.connectionLimit
|
|
622
|
+
})`;
|
|
623
|
+
}
|
|
624
|
+
return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} limit=${
|
|
625
|
+
this.opts.connectionLimit
|
|
626
|
+
})`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
toString() {
|
|
630
|
+
return `active=${this.activeConnections()} idle=${this.idleConnections()} limit=${this.opts.connectionLimit}`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
//*****************************************************************
|
|
634
|
+
// public methods
|
|
635
|
+
//*****************************************************************
|
|
636
|
+
|
|
637
|
+
get closed() {
|
|
638
|
+
return this.#closed;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Get current total connection number.
|
|
643
|
+
* @return {number}
|
|
644
|
+
*/
|
|
645
|
+
totalConnections() {
|
|
646
|
+
return this.activeConnections() + this.idleConnections();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Get current active connections.
|
|
651
|
+
* @return {number}
|
|
652
|
+
*/
|
|
653
|
+
activeConnections() {
|
|
654
|
+
let counter = 0;
|
|
655
|
+
for (const connection of Object.values(this.#activeConnections)) {
|
|
656
|
+
if (connection) counter++;
|
|
657
|
+
}
|
|
658
|
+
return counter;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Get current idle connection number.
|
|
663
|
+
* @return {number}
|
|
664
|
+
*/
|
|
665
|
+
idleConnections() {
|
|
666
|
+
return this.#idleConnections.length;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get current stacked connection request.
|
|
671
|
+
* @return {number}
|
|
672
|
+
*/
|
|
673
|
+
taskQueueSize() {
|
|
674
|
+
return this.#requests.length;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
escape(value) {
|
|
678
|
+
return Utils.escape(this.opts.connOptions, this._searchInfo(), value);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
escapeId(value) {
|
|
682
|
+
return Utils.escapeId(this.opts.connOptions, this._searchInfo(), value);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
//*****************************************************************
|
|
686
|
+
// promise methods
|
|
687
|
+
//*****************************************************************
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Retrieve a connection from the pool.
|
|
691
|
+
* Create a new one if limit is not reached.
|
|
692
|
+
* wait until acquireTimeout.
|
|
693
|
+
* @param cmdParam for stackTrace error
|
|
694
|
+
* @param {Function} callback - Callback function(err, conn)
|
|
695
|
+
*/
|
|
696
|
+
getConnection(cmdParam, callback) {
|
|
697
|
+
if (typeof cmdParam === 'function') {
|
|
698
|
+
callback = cmdParam;
|
|
699
|
+
cmdParam = {};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (this.#closed) {
|
|
703
|
+
const err = Errors.createError(
|
|
704
|
+
'pool is closed',
|
|
705
|
+
Errors.ER_POOL_ALREADY_CLOSED,
|
|
706
|
+
null,
|
|
707
|
+
'HY000',
|
|
708
|
+
cmdParam === null ? null : cmdParam.sql,
|
|
709
|
+
false,
|
|
710
|
+
cmdParam.stack
|
|
711
|
+
);
|
|
712
|
+
callback(err);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
this._acquireIdleConnection((err, conn) => {
|
|
717
|
+
if (!err && conn) {
|
|
718
|
+
// connection is available
|
|
719
|
+
this.emit('acquire', conn);
|
|
720
|
+
callback(null, conn);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (this.#closed) {
|
|
725
|
+
callback(
|
|
726
|
+
Errors.createError(
|
|
727
|
+
'Cannot add request to pool, pool is closed',
|
|
728
|
+
Errors.ER_POOL_ALREADY_CLOSED,
|
|
729
|
+
null,
|
|
730
|
+
'HY000',
|
|
731
|
+
cmdParam === null ? null : cmdParam.sql,
|
|
732
|
+
false,
|
|
733
|
+
cmdParam.stack
|
|
734
|
+
)
|
|
735
|
+
);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// no idle connection available
|
|
740
|
+
// creates a new connection if the limit is not reached
|
|
741
|
+
setImmediate(this.emit.bind(this, 'validateSize'));
|
|
742
|
+
|
|
743
|
+
// stack request
|
|
744
|
+
setImmediate(this.emit.bind(this, 'enqueue'));
|
|
745
|
+
const request = new Request(
|
|
746
|
+
Date.now() + this.opts.acquireTimeout,
|
|
747
|
+
cmdParam.stack,
|
|
748
|
+
(conn) => callback(null, conn),
|
|
749
|
+
(err) => callback(err)
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
this.#requests.push(request);
|
|
753
|
+
|
|
754
|
+
if (!this.#requestTimeoutId) {
|
|
755
|
+
this.#requestTimeoutId = setTimeout(this._checkRequestTimeouts.bind(this), this.opts.acquireTimeout);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Close all connection in pool
|
|
762
|
+
* Ends in multiple step :
|
|
763
|
+
* - close idle connections
|
|
764
|
+
* - ensure that no new request is possible
|
|
765
|
+
* (active connection release are automatically closed on release)
|
|
766
|
+
* - if remaining, after 10 seconds, close remaining active connections
|
|
767
|
+
*
|
|
768
|
+
* @return Promise
|
|
769
|
+
*/
|
|
770
|
+
end() {
|
|
771
|
+
if (this.#closed) {
|
|
772
|
+
return Promise.reject(Errors.createError('pool is already closed', Errors.ER_POOL_ALREADY_CLOSED));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
this.#closed = true;
|
|
776
|
+
clearInterval(this.#unusedConnectionRemoverId);
|
|
777
|
+
clearInterval(this._managePoolSizeTask);
|
|
778
|
+
clearTimeout(this._connectionCreationTask);
|
|
779
|
+
clearTimeout(this.#requestTimeoutId);
|
|
780
|
+
|
|
781
|
+
const cmdParam = {};
|
|
782
|
+
if (this.opts.trace) Error.captureStackTrace(cmdParam);
|
|
783
|
+
//close unused connections
|
|
784
|
+
const idleConnectionsEndings = [];
|
|
785
|
+
let conn;
|
|
786
|
+
while ((conn = this.#idleConnections.shift())) {
|
|
787
|
+
idleConnectionsEndings.push(new Promise(conn.forceEnd.bind(conn, cmdParam)));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
clearTimeout(this.#requestTimeoutId);
|
|
791
|
+
this.#requestTimeoutId = null;
|
|
792
|
+
|
|
793
|
+
//reject all waiting task
|
|
794
|
+
if (!this.#requests.isEmpty()) {
|
|
795
|
+
const err = Errors.createError(
|
|
796
|
+
'pool is ending, connection request aborted',
|
|
797
|
+
Errors.ER_CLOSING_POOL,
|
|
798
|
+
null,
|
|
799
|
+
'HY000',
|
|
800
|
+
null,
|
|
801
|
+
false,
|
|
802
|
+
cmdParam.stack
|
|
803
|
+
);
|
|
804
|
+
let task;
|
|
805
|
+
while ((task = this.#requests.shift())) {
|
|
806
|
+
task.reject(err);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
const pool = this;
|
|
810
|
+
return Promise.all(idleConnectionsEndings).then(async () => {
|
|
811
|
+
if (pool.activeConnections() > 0) {
|
|
812
|
+
// wait up to 10 seconds, that active connection are released
|
|
813
|
+
let remaining = 100;
|
|
814
|
+
while (remaining-- > 0) {
|
|
815
|
+
if (pool.activeConnections() > 0) {
|
|
816
|
+
await new Promise((res) => setTimeout(() => res(), 100));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// force close any remaining active connections
|
|
821
|
+
for (const connection of Object.values(pool.#activeConnections)) {
|
|
822
|
+
if (connection) connection.destroy();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return Promise.resolve();
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
_cleanupConnection(conn, reason = '') {
|
|
830
|
+
if (!conn) return;
|
|
831
|
+
|
|
832
|
+
this._endLeak(conn);
|
|
833
|
+
delete this.#activeConnections[conn.threadId];
|
|
834
|
+
|
|
835
|
+
try {
|
|
836
|
+
// using end in case pool ends while connection succeed without still having function wrappers
|
|
837
|
+
const endingFct = conn.forceEnd ? conn.forceEnd : conn.end;
|
|
838
|
+
endingFct.call(
|
|
839
|
+
conn,
|
|
840
|
+
null,
|
|
841
|
+
() => this.emit('connectionClosed', { threadId: conn.threadId, reason }),
|
|
842
|
+
() => {}
|
|
843
|
+
);
|
|
844
|
+
} catch (err) {
|
|
845
|
+
this.emit('error', new Error(`Failed to cleanup connection: ${err.message}`));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (this.totalConnections() === 0) {
|
|
849
|
+
this._stopConnectionReaping();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
this.emit('validateSize');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Handles the release of a connection back to the pool
|
|
857
|
+
* @param {Connection} conn - The connection to release
|
|
858
|
+
* @param {Function} callback - Callback function when complete
|
|
859
|
+
*/
|
|
860
|
+
_handleRelease(conn, callback) {
|
|
861
|
+
callback = callback || function () {};
|
|
862
|
+
|
|
863
|
+
// Handle special cases first
|
|
864
|
+
if (this.#closed || !conn.isValid()) {
|
|
865
|
+
this._destroy(conn);
|
|
866
|
+
callback();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Skip transaction state reset if configured
|
|
871
|
+
if (this.opts.noControlAfterUse) {
|
|
872
|
+
this.release(conn);
|
|
873
|
+
callback();
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Reset connection state before returning to pool
|
|
878
|
+
const resetFunction = this._getRevertFunction(conn);
|
|
879
|
+
|
|
880
|
+
resetFunction((err) => {
|
|
881
|
+
if (err) {
|
|
882
|
+
this._destroy(conn);
|
|
883
|
+
} else {
|
|
884
|
+
this.release(conn);
|
|
885
|
+
}
|
|
886
|
+
callback();
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Get the appropriate function to reset connection state
|
|
892
|
+
* @returns {Function} Function that takes a callback
|
|
893
|
+
*/
|
|
894
|
+
_getRevertFunction(conn) {
|
|
895
|
+
const canUseReset =
|
|
896
|
+
this.opts.resetAfterUse &&
|
|
897
|
+
conn.info.isMariaDB() &&
|
|
898
|
+
((conn.info.serverVersion.minor === 2 && conn.info.hasMinVersion(10, 2, 22)) ||
|
|
899
|
+
conn.info.hasMinVersion(10, 3, 13));
|
|
900
|
+
|
|
901
|
+
return canUseReset
|
|
902
|
+
? (callback) => conn.reset({}, callback)
|
|
903
|
+
: (callback) =>
|
|
904
|
+
conn.changeTransaction(
|
|
905
|
+
{ sql: 'ROLLBACK' },
|
|
906
|
+
() => callback(null),
|
|
907
|
+
(err) => callback(err)
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Sets up leak detection for a connection
|
|
913
|
+
* @param {Connection} conn - The connection to monitor
|
|
914
|
+
*/
|
|
915
|
+
_startLeakDetection(conn) {
|
|
916
|
+
conn.lastUse = Date.now();
|
|
917
|
+
conn.leaked = false;
|
|
918
|
+
|
|
919
|
+
// Set timeout to detect potential leaks
|
|
920
|
+
conn.leakProcess = setTimeout(
|
|
921
|
+
() => {
|
|
922
|
+
conn.leaked = true;
|
|
923
|
+
const unusedTime = Date.now() - conn.lastUse;
|
|
924
|
+
|
|
925
|
+
// Log warning about potential leak
|
|
926
|
+
conn.opts.logger.warning(
|
|
927
|
+
`A possible connection leak on thread ${conn.info.threadId} ` +
|
|
928
|
+
`(connection not returned to pool for ${unusedTime}ms). ` +
|
|
929
|
+
`Has connection.release() been called?${this._errorMsgAddon()}`
|
|
930
|
+
);
|
|
931
|
+
},
|
|
932
|
+
this.opts.leakDetectionTimeout,
|
|
933
|
+
conn
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
class Request {
|
|
939
|
+
constructor(timeout, stack, resolver, rejecter) {
|
|
940
|
+
this.timeout = timeout;
|
|
941
|
+
this.stack = stack;
|
|
942
|
+
this.resolver = resolver;
|
|
943
|
+
this.rejecter = rejecter;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
reject(err) {
|
|
947
|
+
process.nextTick(this.rejecter, err);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
module.exports = Pool;
|