@steve31415/baselib 3.51.0 → 3.52.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/dist/db.d.ts +8 -0
- package/dist/db.js +88 -0
- package/package.json +1 -1
package/dist/db.d.ts
CHANGED
|
@@ -72,7 +72,15 @@ export interface PoolLoggingOptions {
|
|
|
72
72
|
/** Epoch ms when the connector's cached Cloud SQL credential expires
|
|
73
73
|
* (`cloudSqlCredentialExpiry`); logged as `credentialRemainingMs`. */
|
|
74
74
|
credentialExpiry?: () => number | undefined;
|
|
75
|
+
/** A new connection that takes at least this long, succeeding or
|
|
76
|
+
* failing, logs its handshake timings (`timeNewConnections`). Default
|
|
77
|
+
* `SLOW_CONNECT_MS`. */
|
|
78
|
+
slowConnectMs?: number;
|
|
75
79
|
}
|
|
80
|
+
/** A healthy Cloud SQL connect takes ~80 ms (2026-10-30); 2 s is well
|
|
81
|
+
* outside that and well inside the 15 s checkout timeout, so a stall that
|
|
82
|
+
* the timeout later kills has already been seen to be slow. */
|
|
83
|
+
export declare const SLOW_CONNECT_MS = 2000;
|
|
76
84
|
/** Pool settings every fleet pool carries; `createPool` applies them and
|
|
77
85
|
* is the way to get them (a hand-built pool spreads this, but prefer
|
|
78
86
|
* `createPool` with `user` / `sessionOptions` over building one). Decided
|
package/dist/db.js
CHANGED
|
@@ -71,6 +71,10 @@ export function cloudSqlCredentialExpiry(connector, instanceOptions) {
|
|
|
71
71
|
* `set local statement_timeout = 0` — migrate() runs each file in its own
|
|
72
72
|
* transaction, so the override scopes to that migration. */
|
|
73
73
|
export const DEFAULT_STATEMENT_TIMEOUT_MS = 10 * 60_000;
|
|
74
|
+
/** A healthy Cloud SQL connect takes ~80 ms (2026-10-30); 2 s is well
|
|
75
|
+
* outside that and well inside the 15 s checkout timeout, so a stall that
|
|
76
|
+
* the timeout later kills has already been seen to be slow. */
|
|
77
|
+
export const SLOW_CONNECT_MS = 2_000;
|
|
74
78
|
/** Pool settings every fleet pool carries; `createPool` applies them and
|
|
75
79
|
* is the way to get them (a hand-built pool spreads this, but prefer
|
|
76
80
|
* `createPool` with `user` / `sessionOptions` over building one). Decided
|
|
@@ -203,6 +207,7 @@ export function logPoolErrors(pool, logger, options = {}) {
|
|
|
203
207
|
return;
|
|
204
208
|
instrumented.add(pool);
|
|
205
209
|
const warn = connectionLossWarner(pool, logger, options.credentialExpiry);
|
|
210
|
+
timeNewConnections(pool, logger, options);
|
|
206
211
|
// Per-client bookkeeping across one checkout: the borrower-side error
|
|
207
212
|
// listener (pg-pool drops its own at checkout) and when the client was
|
|
208
213
|
// taken and given back.
|
|
@@ -267,6 +272,89 @@ export function logPoolErrors(pool, logger, options = {}) {
|
|
|
267
272
|
});
|
|
268
273
|
});
|
|
269
274
|
}
|
|
275
|
+
const CONNECT_TIMING_MESSAGE = 'database pool: new connection was slow or failed; handshake timings';
|
|
276
|
+
/** Time every new connection's handshake and log any that took
|
|
277
|
+
* `slowConnectMs` or longer, whether it then succeeded or failed (INFO,
|
|
278
|
+
* `kind=db-connect-timing`): how far it got (`reached`) and when
|
|
279
|
+
* each step finished, in ms from the start — `tcpMs` (socket connected),
|
|
280
|
+
* `tlsMs` (TLS done; the Cloud SQL connector's socket only), `authMs`
|
|
281
|
+
* (the server accepted the login; with IAM authentication Cloud SQL checks
|
|
282
|
+
* the token here). The point is the step a stall sits in: before `tcp` is
|
|
283
|
+
* the network path, between `tcp` and `tls` the Cloud SQL front end or the
|
|
284
|
+
* certificate, between `tls`/`tcp` and `authenticated` the database login.
|
|
285
|
+
* pg-pool's own events cannot say this, and a handshake whose checkout
|
|
286
|
+
* already timed out ends silently there (coder 2026-11-02 19:07Z: a 15 s
|
|
287
|
+
* Cloud SQL handshake left only the waiter's timeout behind). The
|
|
288
|
+
* waiter's WARN (`db-connection-loss`) stays the alarm; this is its
|
|
289
|
+
* detail, hence INFO. Works by subclassing the pool's client class
|
|
290
|
+
* (pg-pool reads `pool.Client` for each new connection; pg 8.x creates
|
|
291
|
+
* the socket in the client's constructor). */
|
|
292
|
+
function timeNewConnections(pool, logger, options) {
|
|
293
|
+
const slowMs = options.slowConnectMs ?? SLOW_CONNECT_MS;
|
|
294
|
+
const holder = pool;
|
|
295
|
+
const Base = holder.Client;
|
|
296
|
+
if (typeof Base !== 'function')
|
|
297
|
+
return;
|
|
298
|
+
const log = logger?.with({ kind: 'db-connect-timing' });
|
|
299
|
+
holder.Client = class TimedClient extends Base {
|
|
300
|
+
#started = Date.now();
|
|
301
|
+
#marks = {};
|
|
302
|
+
#reached = 'started';
|
|
303
|
+
constructor(...args) {
|
|
304
|
+
super(...args);
|
|
305
|
+
const connection = this.connection;
|
|
306
|
+
const mark = (step) => () => {
|
|
307
|
+
this.#marks[step] ??= Date.now() - this.#started;
|
|
308
|
+
this.#reached = step;
|
|
309
|
+
};
|
|
310
|
+
connection?.stream?.once?.('connect', mark('tcp'));
|
|
311
|
+
connection?.stream?.once?.('secureConnect', mark('tls'));
|
|
312
|
+
connection?.once?.('authenticationOk', mark('authenticated'));
|
|
313
|
+
}
|
|
314
|
+
#report(err) {
|
|
315
|
+
const elapsedMs = Date.now() - this.#started;
|
|
316
|
+
// A quick failure (refused, rejected) has the waiter's WARN and no
|
|
317
|
+
// stall to locate.
|
|
318
|
+
if (elapsedMs < slowMs)
|
|
319
|
+
return;
|
|
320
|
+
if (!err)
|
|
321
|
+
this.#reached = 'ready';
|
|
322
|
+
const expiry = options.credentialExpiry?.();
|
|
323
|
+
const meta = {
|
|
324
|
+
outcome: err ? 'failed' : 'slow',
|
|
325
|
+
reached: this.#reached,
|
|
326
|
+
elapsedMs,
|
|
327
|
+
tcpMs: this.#marks.tcp,
|
|
328
|
+
tlsMs: this.#marks.tls,
|
|
329
|
+
authMs: this.#marks.authenticated,
|
|
330
|
+
pool: { total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount, max: pool.options.max },
|
|
331
|
+
...(options.credentialExpiry ? (expiry === undefined ? { credentialUnknown: true } : { credentialRemainingMs: expiry - Date.now() }) : {}),
|
|
332
|
+
...(err ? { error: err } : {}),
|
|
333
|
+
};
|
|
334
|
+
if (log)
|
|
335
|
+
log.info(CONNECT_TIMING_MESSAGE, meta);
|
|
336
|
+
else
|
|
337
|
+
console.info(CONNECT_TIMING_MESSAGE, { kind: 'db-connect-timing', ...processMeta(), ...meta, ...(err ? { error: serializeError(err) } : {}) });
|
|
338
|
+
}
|
|
339
|
+
connect(callback) {
|
|
340
|
+
if (typeof callback === 'function') {
|
|
341
|
+
// pg calls back (null, client) on success; pass every argument on.
|
|
342
|
+
const done = callback;
|
|
343
|
+
return super.connect(((err, ...rest) => {
|
|
344
|
+
this.#report(err ?? undefined);
|
|
345
|
+
done(err, ...rest);
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
return super.connect().then((client) => {
|
|
349
|
+
this.#report(undefined);
|
|
350
|
+
return client;
|
|
351
|
+
}, (err) => {
|
|
352
|
+
this.#report(err);
|
|
353
|
+
throw err;
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
270
358
|
const probed = new WeakSet();
|
|
271
359
|
/** Hand out no pooled client that died while idle. A client idle for
|
|
272
360
|
* `afterIdleMs` or more runs `select 1` (bounded by `timeoutMs`) before
|
package/package.json
CHANGED