@mark1russell7/service 0.5.11 → 0.5.13
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 +110 -1
- package/build/{builder-Dn0cby6Q.d.ts → builder-DawJhq4p.d.ts} +2 -2
- package/build/core-BPLRDcUJ.d.ts +226 -0
- package/build/dial-DA8vJfh2.d.ts +16 -0
- package/build/grpc.d.ts +307 -0
- package/build/grpc.js +859 -0
- package/build/grpc.js.map +1 -0
- package/build/http-client.d.ts +248 -0
- package/build/http-client.js +612 -0
- package/build/http-client.js.map +1 -0
- package/build/http.d.ts +1 -1
- package/build/index.d.ts +8 -149
- package/build/index.js +155 -11
- package/build/index.js.map +1 -1
- package/build/{lifecycle-CGEegMI0.d.ts → lifecycle-fgYfxrHK.d.ts} +17 -2
- package/build/machine-BhTRu3u9.js +552 -0
- package/build/machine-BhTRu3u9.js.map +1 -0
- package/build/mongodb.d.ts +307 -0
- package/build/mongodb.js +688 -0
- package/build/mongodb.js.map +1 -0
- package/build/pg.d.ts +230 -0
- package/build/pg.js +531 -0
- package/build/pg.js.map +1 -0
- package/build/queue-snRHK3Jm.d.ts +150 -0
- package/build/rabbitmq.d.ts +294 -0
- package/build/rabbitmq.js +748 -0
- package/build/rabbitmq.js.map +1 -0
- package/build/redis.d.ts +24 -143
- package/build/redis.js +76 -315
- package/build/redis.js.map +1 -1
- package/build/testing.d.ts +2 -2
- package/build/websocket.d.ts +275 -0
- package/build/websocket.js +780 -0
- package/build/websocket.js.map +1 -0
- package/package.json +46 -1
package/build/redis.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { realClock } from "./clock.js";
|
|
2
2
|
import { heartbeat } from "./heartbeat.js";
|
|
3
3
|
import { i as heartbeatDetection } from "./timing-DGRi3XCI.js";
|
|
4
|
-
import { t as mayReissue } from "./operation-DL5RxFds.js";
|
|
5
4
|
import { backoffDelay } from "./backoff.js";
|
|
6
|
-
import {
|
|
7
|
-
import { connect } from "node:net";
|
|
5
|
+
import { C as notSent, D as readyWait, F as timeProblems, G as immediate, J as keepaliveSilent, K as keepaliveAnswered, M as sendWithReissue, N as socketEnds, O as retryBackoff, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, d as clientProblems, f as clientRecorder, h as finiteMs, j as roundTripCheck, k as retryProblems, l as attachClient, m as dialCheck, n as closeTimer, q as keepaliveReset, r as transition, t as clientKnob, u as clientBounds, w as operationTable, x as missesProblems, y as localStateCheck } from "./machine-BhTRu3u9.js";
|
|
8
6
|
//#region src/redis/connection.ts
|
|
9
7
|
/**
|
|
10
8
|
* This factory makes an ioredis client. It loads ioredis only when a client connects, thus a
|
|
@@ -15,13 +13,6 @@ const ioredis = async (options) => {
|
|
|
15
13
|
};
|
|
16
14
|
//#endregion
|
|
17
15
|
//#region src/redis/meets.ts
|
|
18
|
-
const never = {
|
|
19
|
-
value: "infinite",
|
|
20
|
-
unit: "s"
|
|
21
|
-
};
|
|
22
|
-
const s = (value) => quantity(Math.round(value * 1e6) / 1e6, "s");
|
|
23
|
-
const seconds = (amount) => amount.value === "infinite" ? Number.POSITIVE_INFINITY : valueIn(amount, "s");
|
|
24
|
-
const plus = (amount, more) => amount.value === "infinite" ? never : s(seconds(amount) + more);
|
|
25
16
|
/**
|
|
26
17
|
* This function gives the manifestations that a Redis client can meet, and the detectors of each, from
|
|
27
18
|
* its knobs. A reset or a FIN arrives on the next segment. A black hole and a host that is gone give
|
|
@@ -29,7 +20,6 @@ const plus = (amount, more) => amount.value === "infinite" ? never : s(seconds(a
|
|
|
29
20
|
* segment. Thus TCP keepalive does not fire on them, and only the client's own timers find them.
|
|
30
21
|
*/
|
|
31
22
|
const meetings = (name, knobs, heartbeat) => {
|
|
32
|
-
const keepalive = plus(knobs.keepalive_idle, 10);
|
|
33
23
|
const watchdog = (situation) => ({
|
|
34
24
|
by: "heartbeat_detection_time",
|
|
35
25
|
within: heartbeat,
|
|
@@ -48,27 +38,17 @@ const meetings = (name, knobs, heartbeat) => {
|
|
|
48
38
|
situation: "in flight",
|
|
49
39
|
effect: "the command fails as indeterminate, and the state of the connection does not change"
|
|
50
40
|
};
|
|
51
|
-
const giveUp =
|
|
52
|
-
|
|
53
|
-
within: knobs.retrans_giveup_time,
|
|
54
|
-
situation: "in flight",
|
|
55
|
-
effect: "the kernel gives up the retransmissions and closes the socket"
|
|
56
|
-
};
|
|
57
|
-
const silent = (manifestation, what) => ({
|
|
41
|
+
const giveUp = kernelGiveUp(knobs.retrans_giveup_time);
|
|
42
|
+
const silent = (manifestation) => ({
|
|
58
43
|
manifestation,
|
|
59
|
-
what,
|
|
44
|
+
what: WHAT[manifestation],
|
|
60
45
|
detectors: [
|
|
61
46
|
command,
|
|
62
47
|
writeoff,
|
|
63
48
|
watchdog("in flight"),
|
|
64
49
|
giveUp,
|
|
65
50
|
watchdog("idle"),
|
|
66
|
-
|
|
67
|
-
by: "keepalive_idle",
|
|
68
|
-
within: keepalive,
|
|
69
|
-
situation: "idle",
|
|
70
|
-
effect: "TCP keepalive gives up, and the socket closes"
|
|
71
|
-
}
|
|
51
|
+
keepaliveSilent(knobs.keepalive_idle)
|
|
72
52
|
]
|
|
73
53
|
});
|
|
74
54
|
const acknowledged = (manifestation, what) => ({
|
|
@@ -79,55 +59,30 @@ const meetings = (name, knobs, heartbeat) => {
|
|
|
79
59
|
writeoff,
|
|
80
60
|
watchdog("in flight"),
|
|
81
61
|
watchdog("idle"),
|
|
82
|
-
|
|
83
|
-
by: "keepalive_idle",
|
|
84
|
-
within: never,
|
|
85
|
-
situation: "idle",
|
|
86
|
-
effect: "the kernel of the server answers each probe"
|
|
87
|
-
}
|
|
62
|
+
keepaliveAnswered()
|
|
88
63
|
]
|
|
89
64
|
});
|
|
90
|
-
const immediate = (situation, effect) => ({
|
|
91
|
-
by: "the next segment",
|
|
92
|
-
within: s(0),
|
|
93
|
-
situation,
|
|
94
|
-
effect
|
|
95
|
-
});
|
|
96
65
|
return [
|
|
97
66
|
{
|
|
98
67
|
manifestation: "rst",
|
|
99
|
-
what:
|
|
68
|
+
what: WHAT.rst,
|
|
100
69
|
detectors: [
|
|
101
70
|
immediate("in flight", "the command fails, and the client connects again, and each attempt is refused"),
|
|
102
71
|
watchdog("idle"),
|
|
103
|
-
|
|
104
|
-
by: "keepalive_idle",
|
|
105
|
-
within: keepalive.value === "infinite" ? never : knobs.keepalive_idle,
|
|
106
|
-
situation: "idle",
|
|
107
|
-
effect: "the first probe gets a reset"
|
|
108
|
-
}
|
|
72
|
+
keepaliveReset(knobs.keepalive_idle)
|
|
109
73
|
]
|
|
110
74
|
},
|
|
111
75
|
{
|
|
112
76
|
manifestation: "fin",
|
|
113
|
-
what:
|
|
77
|
+
what: WHAT.fin,
|
|
114
78
|
detectors: [immediate("in flight", "the close arrives, and the command fails"), immediate("idle", "the close arrives")]
|
|
115
79
|
},
|
|
116
|
-
silent("blackhole"
|
|
117
|
-
silent("freeze-host"
|
|
118
|
-
acknowledged("freeze-process", "
|
|
80
|
+
silent("blackhole"),
|
|
81
|
+
silent("freeze-host"),
|
|
82
|
+
acknowledged("freeze-process", WHAT["freeze-process"]),
|
|
119
83
|
acknowledged("app-stall", "the server stops its replies for a time, for example a long command, and its kernel stays up")
|
|
120
84
|
];
|
|
121
85
|
};
|
|
122
|
-
/** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */
|
|
123
|
-
const firstDetector = (meeting, situation) => {
|
|
124
|
-
return meeting.detectors.filter((detector) => detector.situation === situation && detector.within.value !== "infinite").sort((a, b) => seconds(a.within) - seconds(b.within))[0] ?? null;
|
|
125
|
-
};
|
|
126
|
-
/** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */
|
|
127
|
-
const unwatched = (meets) => meets.flatMap((meeting) => ["in flight", "idle"].flatMap((situation) => firstDetector(meeting, situation) === null ? [{
|
|
128
|
-
manifestation: meeting.manifestation,
|
|
129
|
-
situation
|
|
130
|
-
}] : []));
|
|
131
86
|
//#endregion
|
|
132
87
|
//#region src/redis/operations.ts
|
|
133
88
|
/**
|
|
@@ -242,63 +197,23 @@ const commandName = (command, args) => {
|
|
|
242
197
|
};
|
|
243
198
|
//#endregion
|
|
244
199
|
//#region src/redis/client.ts
|
|
245
|
-
const ms = (amount) => amount.value === "infinite" ? Number.POSITIVE_INFINITY : valueIn(amount, "ms");
|
|
246
|
-
const finite = (amount) => {
|
|
247
|
-
const value = ms(amount);
|
|
248
|
-
return Number.isFinite(value) ? value : void 0;
|
|
249
|
-
};
|
|
250
|
-
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
251
|
-
/**
|
|
252
|
-
* This function gives the backoff of the tries to connect: from `retry_base`, doubling to
|
|
253
|
-
* `retry_cap`, each with a jitter up to the base. The client rounds each delay to a whole millisecond.
|
|
254
|
-
*/
|
|
255
|
-
const retryBackoff = (knobs) => ({
|
|
256
|
-
base: ms(knobs.retry_base),
|
|
257
|
-
factor: 2,
|
|
258
|
-
cap: ms(knobs.retry_cap),
|
|
259
|
-
jitter: {
|
|
260
|
-
kind: "add",
|
|
261
|
-
amount: ms(knobs.retry_base)
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
200
|
/** This function gives the problems of a spec: the knobs that the client cannot use. */
|
|
265
201
|
const specProblems = (name, spec) => {
|
|
266
202
|
const problems = [];
|
|
267
203
|
const { knobs } = spec;
|
|
268
|
-
|
|
204
|
+
problems.push(...missesProblems(name, spec.heartbeatMisses, "watchdog"));
|
|
269
205
|
if (knobs.max_connection_age.value !== "infinite") problems.push(`${name} cannot replace its connection at an age: max_connection_age must be "infinite"`);
|
|
270
|
-
|
|
206
|
+
problems.push(...timeProblems(name, knobs));
|
|
271
207
|
if (ms(knobs.request_timeout) <= 0) problems.push(`${name}'s request_timeout is not above zero: each command must have a bound`);
|
|
272
208
|
if (ms(knobs.heartbeat_timeout) <= 0) problems.push(`${name}'s heartbeat_timeout is not above zero`);
|
|
273
|
-
|
|
209
|
+
problems.push(...retryProblems(name, knobs));
|
|
274
210
|
return problems;
|
|
275
211
|
};
|
|
276
212
|
/** This dial function opens a TCP connection, sends an inline PING, and waits for +PONG. */
|
|
277
|
-
const tcpDial = (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
const socket = connect({
|
|
283
|
-
host,
|
|
284
|
-
port
|
|
285
|
-
});
|
|
286
|
-
let reply = "";
|
|
287
|
-
const end = (error) => {
|
|
288
|
-
signal.removeEventListener("abort", onAbort);
|
|
289
|
-
socket.destroy();
|
|
290
|
-
if (error === void 0) resolve();
|
|
291
|
-
else reject(error);
|
|
292
|
-
};
|
|
293
|
-
const onAbort = () => end(/* @__PURE__ */ new Error("the dial was aborted"));
|
|
294
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
295
|
-
socket.once("connect", () => socket.write("PING\r\n"));
|
|
296
|
-
socket.on("data", (chunk) => {
|
|
297
|
-
reply += chunk.toString();
|
|
298
|
-
if (reply.includes("\r\n")) end(reply.startsWith("+PONG") ? void 0 : /* @__PURE__ */ new Error(`the reply was ${reply.trim()}`));
|
|
299
|
-
});
|
|
300
|
-
socket.once("error", (error) => end(error));
|
|
301
|
-
socket.once("close", () => end(/* @__PURE__ */ new Error("the server closed the dial")));
|
|
213
|
+
const tcpDial = helloDial("PING\r\n", (received) => {
|
|
214
|
+
const reply = received.toString();
|
|
215
|
+
if (!reply.includes("\r\n")) return "more";
|
|
216
|
+
return reply.startsWith("+PONG") ? null : /* @__PURE__ */ new Error(`the reply was ${reply.trim()}`);
|
|
302
217
|
});
|
|
303
218
|
/**
|
|
304
219
|
* This function makes a Redis client. It is a dependency of a service, with ioredis under it:
|
|
@@ -309,6 +224,9 @@ const tcpDial = (host, port, signal) => new Promise((resolve, reject) => {
|
|
|
309
224
|
* - It records the server's id of each connection at `ready` (D1).
|
|
310
225
|
* - A watchdog sends a PING in the band of the connection, and it connects again after the misses
|
|
311
226
|
* of the declaration. It also starts a client that stopped (principle 19).
|
|
227
|
+
*
|
|
228
|
+
* The outcomes, the second try, the commands in flight, the bounded close and the checks come
|
|
229
|
+
* from the core of the clients. This entry gives what ioredis does in its own way.
|
|
312
230
|
*/
|
|
313
231
|
const redisClient = (name, spec, options = {}) => {
|
|
314
232
|
const problems = specProblems(name, spec);
|
|
@@ -319,10 +237,7 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
319
237
|
const create = options.create ?? ioredis;
|
|
320
238
|
const dial = options.dial ?? tcpDial;
|
|
321
239
|
const turn = options.turn ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
322
|
-
const operations =
|
|
323
|
-
...REDIS_OPERATIONS,
|
|
324
|
-
...spec.operations
|
|
325
|
-
};
|
|
240
|
+
const operations = operationTable(REDIS_OPERATIONS, spec.operations);
|
|
326
241
|
const requestTimeout = ms(knobs.request_timeout);
|
|
327
242
|
const detection = heartbeatDetection(knobs.heartbeat_interval, knobs.heartbeat_timeout, spec.heartbeatMisses);
|
|
328
243
|
const backoff = retryBackoff(knobs);
|
|
@@ -330,18 +245,10 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
330
245
|
let status = "none";
|
|
331
246
|
let closing = false;
|
|
332
247
|
const joins = [];
|
|
333
|
-
const
|
|
334
|
-
const
|
|
248
|
+
const flight = inFlight();
|
|
249
|
+
const readied = readyWait(clock, () => connection?.status === "ready");
|
|
335
250
|
const listeners = [];
|
|
336
|
-
const record = (
|
|
337
|
-
at: clock.now(),
|
|
338
|
-
machine: "client",
|
|
339
|
-
instance: name,
|
|
340
|
-
from,
|
|
341
|
-
to,
|
|
342
|
-
on,
|
|
343
|
-
...detail === void 0 ? {} : { detail }
|
|
344
|
-
});
|
|
251
|
+
const record = clientRecorder(options.record, clock, name);
|
|
345
252
|
let retrySince = null;
|
|
346
253
|
const retryStrategy = (times) => {
|
|
347
254
|
const now = clock.now();
|
|
@@ -361,8 +268,8 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
361
268
|
lazyConnect: true,
|
|
362
269
|
connectTimeout: ms(knobs.connect_bound),
|
|
363
270
|
commandTimeout: requestTimeout,
|
|
364
|
-
socketTimeout:
|
|
365
|
-
keepAlive:
|
|
271
|
+
socketTimeout: finiteMs(knobs.driver_writeoff_time),
|
|
272
|
+
keepAlive: finiteMs(knobs.keepalive_idle) ?? false,
|
|
366
273
|
enableOfflineQueue: false,
|
|
367
274
|
autoResendUnfulfilledCommands: false,
|
|
368
275
|
maxRetriesPerRequest: null,
|
|
@@ -378,11 +285,7 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
378
285
|
error: text,
|
|
379
286
|
reply: true
|
|
380
287
|
};
|
|
381
|
-
if (/isn't writeable|enableOfflineQueue/i.test(text)) return
|
|
382
|
-
outcome: "error",
|
|
383
|
-
error: text,
|
|
384
|
-
reply: false
|
|
385
|
-
};
|
|
288
|
+
if (/isn't writeable|enableOfflineQueue/i.test(text)) return notSent(text);
|
|
386
289
|
return {
|
|
387
290
|
outcome: "indeterminate",
|
|
388
291
|
error: text
|
|
@@ -408,22 +311,16 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
408
311
|
generation,
|
|
409
312
|
at,
|
|
410
313
|
clientId,
|
|
411
|
-
|
|
412
|
-
localPort: socket?.localPort ?? null,
|
|
413
|
-
remoteAddress: socket?.remoteAddress ?? null,
|
|
414
|
-
remotePort: socket?.remotePort ?? null
|
|
314
|
+
...socketEnds(socket)
|
|
415
315
|
});
|
|
416
316
|
record("ready", "ready", "join", `generation ${generation}, client id ${clientId ?? "unknown"}`);
|
|
417
|
-
|
|
317
|
+
readied.wake();
|
|
418
318
|
};
|
|
419
319
|
const onStatus = (to) => () => {
|
|
420
320
|
const from = status;
|
|
421
321
|
status = to;
|
|
422
322
|
record(from, to, `status.${to}`);
|
|
423
|
-
if (to === "close" || to === "end")
|
|
424
|
-
outcome: "indeterminate",
|
|
425
|
-
error: `the connection closed with the command in flight (${from})`
|
|
426
|
-
});
|
|
323
|
+
if (to === "close" || to === "end") flight.settleAll(`the connection closed with the command in flight (${from})`);
|
|
427
324
|
if (to === "ready") onReady();
|
|
428
325
|
};
|
|
429
326
|
const watch = (c) => {
|
|
@@ -443,96 +340,27 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
443
340
|
c.on("error", onError);
|
|
444
341
|
listeners.push(["error", onError]);
|
|
445
342
|
};
|
|
446
|
-
/** This function waits until the connection is ready, for `within` milliseconds at most. It gives true when it is ready. */
|
|
447
|
-
const waitReady = (within, signal) => new Promise((resolve) => {
|
|
448
|
-
if (connection?.status === "ready") {
|
|
449
|
-
resolve(true);
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
let cancel = () => {};
|
|
453
|
-
const done = (value) => {
|
|
454
|
-
readyWaiters.delete(wake);
|
|
455
|
-
cancel();
|
|
456
|
-
signal?.removeEventListener("abort", aborted);
|
|
457
|
-
resolve(value);
|
|
458
|
-
};
|
|
459
|
-
const wake = () => done(true);
|
|
460
|
-
const aborted = () => done(false);
|
|
461
|
-
readyWaiters.add(wake);
|
|
462
|
-
cancel = clock.after(within, () => done(connection?.status === "ready"));
|
|
463
|
-
signal?.addEventListener("abort", aborted, { once: true });
|
|
464
|
-
});
|
|
465
343
|
const attempt = (command, args, signal) => {
|
|
466
344
|
const c = connection;
|
|
467
|
-
if (c === null) return Promise.resolve({
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
});
|
|
472
|
-
if (c.status !== "ready") return Promise.resolve({
|
|
473
|
-
outcome: "error",
|
|
474
|
-
error: `${name} is ${c.status}, not ready: the command did not leave the client`,
|
|
475
|
-
reply: false
|
|
476
|
-
});
|
|
477
|
-
if (signal?.aborted) return Promise.resolve({
|
|
478
|
-
outcome: "error",
|
|
479
|
-
error: "the caller aborted the command before it left the client",
|
|
480
|
-
reply: false
|
|
481
|
-
});
|
|
482
|
-
return new Promise((resolve) => {
|
|
483
|
-
let settled = false;
|
|
484
|
-
const settle = (value) => {
|
|
485
|
-
if (settled) return;
|
|
486
|
-
settled = true;
|
|
487
|
-
inFlight.delete(settle);
|
|
488
|
-
signal?.removeEventListener("abort", aborted);
|
|
489
|
-
resolve(value);
|
|
490
|
-
};
|
|
491
|
-
const aborted = () => settle({
|
|
492
|
-
outcome: "indeterminate",
|
|
493
|
-
error: "the caller abandoned the command after it was sent"
|
|
494
|
-
});
|
|
495
|
-
inFlight.add(settle);
|
|
496
|
-
signal?.addEventListener("abort", aborted, { once: true });
|
|
345
|
+
if (c === null) return Promise.resolve(notSent(`${name} has not connected`));
|
|
346
|
+
if (c.status !== "ready") return Promise.resolve(notSent(`${name} is ${c.status}, not ready: the command did not leave the client`));
|
|
347
|
+
if (signal?.aborted) return Promise.resolve(notSent("the caller aborted the command before it left the client"));
|
|
348
|
+
return flight.track(signal, "the caller abandoned the command after it was sent", (settle) => {
|
|
497
349
|
c.call(command, ...args).then((value) => settle({
|
|
498
350
|
outcome: "ok",
|
|
499
351
|
value
|
|
500
352
|
}), (error) => settle(failed(error)));
|
|
501
353
|
});
|
|
502
354
|
};
|
|
503
|
-
const send =
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
};
|
|
513
|
-
const again = !(first.outcome === "error" && first.reply) && operation !== void 0 && signal?.aborted !== true && mayReissue(operation, quantity((clock.now() - started) / 1e3, "s"));
|
|
514
|
-
const left = requestTimeout - (clock.now() - started);
|
|
515
|
-
if (!again || left <= 0 || !await waitReady(left, signal)) return {
|
|
516
|
-
...first,
|
|
517
|
-
attempts: 1
|
|
518
|
-
};
|
|
519
|
-
record(status, status, "command.reissue", commandName(command, args));
|
|
520
|
-
const bound = new AbortController();
|
|
521
|
-
const end = clock.after(Math.max(0, requestTimeout - (clock.now() - started)), () => bound.abort());
|
|
522
|
-
const forward = () => bound.abort();
|
|
523
|
-
signal?.addEventListener("abort", forward, { once: true });
|
|
524
|
-
const second = await attempt(command, args, bound.signal);
|
|
525
|
-
end();
|
|
526
|
-
signal?.removeEventListener("abort", forward);
|
|
527
|
-
return second.outcome === "ok" ? {
|
|
528
|
-
outcome: "ok",
|
|
529
|
-
value: second.value,
|
|
530
|
-
attempts: 2
|
|
531
|
-
} : {
|
|
532
|
-
...second,
|
|
533
|
-
attempts: 2
|
|
534
|
-
};
|
|
535
|
-
};
|
|
355
|
+
const send = (command, args = [], sendOptions = {}) => sendWithReissue({
|
|
356
|
+
clock,
|
|
357
|
+
bound: requestTimeout,
|
|
358
|
+
operation: operations[commandName(command, args)],
|
|
359
|
+
options: sendOptions,
|
|
360
|
+
attempt: (signal) => attempt(command, args, signal),
|
|
361
|
+
beforeAgain: (left, signal) => readied.wait(left, signal),
|
|
362
|
+
onReissue: () => record(status, status, "command.reissue", commandName(command, args))
|
|
363
|
+
});
|
|
536
364
|
let watchdog = null;
|
|
537
365
|
const startWatchdog = () => heartbeat({
|
|
538
366
|
interval: ms(knobs.heartbeat_interval),
|
|
@@ -577,15 +405,8 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
577
405
|
watchdog = null;
|
|
578
406
|
const c = connection;
|
|
579
407
|
if (c === null) return;
|
|
580
|
-
const quit = c.quit().then(() => "quit", () => "quit");
|
|
581
|
-
let cancel = () => {};
|
|
582
|
-
const bound = new Promise((resolve) => {
|
|
583
|
-
cancel = clock.after(ms(knobs.close_bound), () => resolve("bound"));
|
|
584
|
-
});
|
|
585
408
|
const startedClose = clock.now();
|
|
586
|
-
|
|
587
|
-
cancel();
|
|
588
|
-
if (first === "bound") record(status, status, "close.bound");
|
|
409
|
+
if (!await withinBound(clock, ms(knobs.close_bound), c.quit())) record(status, status, "close.bound");
|
|
589
410
|
if (c.status !== "end") c.disconnect(false);
|
|
590
411
|
const ended = (within) => c.status === "end" ? Promise.resolve() : new Promise((resolve) => {
|
|
591
412
|
let stop = () => {};
|
|
@@ -605,10 +426,7 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
605
426
|
}
|
|
606
427
|
for (const [event, listener] of listeners) c.off(event, listener);
|
|
607
428
|
listeners.length = 0;
|
|
608
|
-
|
|
609
|
-
outcome: "indeterminate",
|
|
610
|
-
error: "the client closed with the command in flight"
|
|
611
|
-
});
|
|
429
|
+
flight.settleAll("the client closed with the command in flight");
|
|
612
430
|
status = "end";
|
|
613
431
|
}
|
|
614
432
|
};
|
|
@@ -631,32 +449,11 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
631
449
|
},
|
|
632
450
|
operations
|
|
633
451
|
};
|
|
634
|
-
const checks = (checkOptions) => {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
name: `${name}.${step}`,
|
|
640
|
-
dependency: name,
|
|
641
|
-
step,
|
|
642
|
-
ownConnection,
|
|
643
|
-
gates: checkOptions.steps[step] ?? null,
|
|
644
|
-
poll: checkOptions.poll
|
|
645
|
-
}, run]);
|
|
646
|
-
};
|
|
647
|
-
declare("local-state", true, () => connection?.status === "ready" ? "ok" : "error");
|
|
648
|
-
declare("round-trip", true, async (signal) => {
|
|
649
|
-
return (await send("PING", [], {
|
|
650
|
-
signal,
|
|
651
|
-
reissue: false
|
|
652
|
-
})).outcome === "ok" ? "ok" : "error";
|
|
653
|
-
});
|
|
654
|
-
declare("dial", false, async (signal) => {
|
|
655
|
-
await dial(spec.host, spec.port, signal);
|
|
656
|
-
return "ok";
|
|
657
|
-
});
|
|
658
|
-
return made;
|
|
659
|
-
};
|
|
452
|
+
const checks = (checkOptions) => stepChecks(name, checkOptions, {
|
|
453
|
+
"local-state": localStateCheck(() => connection?.status === "ready"),
|
|
454
|
+
"round-trip": roundTripCheck((sendOptions) => send("PING", [], sendOptions)),
|
|
455
|
+
dial: dialCheck(dial, spec.host, spec.port)
|
|
456
|
+
});
|
|
660
457
|
return {
|
|
661
458
|
name,
|
|
662
459
|
declaration,
|
|
@@ -668,23 +465,8 @@ const redisClient = (name, spec, options = {}) => {
|
|
|
668
465
|
joins,
|
|
669
466
|
send,
|
|
670
467
|
checks,
|
|
671
|
-
attach(svc, checkOptions)
|
|
672
|
-
|
|
673
|
-
for (const [check, run] of checks(checkOptions)) svc.check(check, run);
|
|
674
|
-
return svc;
|
|
675
|
-
},
|
|
676
|
-
problems(description) {
|
|
677
|
-
const found = [];
|
|
678
|
-
const teardown = description.shutdown.phases.find((phase) => phase.name === "tear down dependencies");
|
|
679
|
-
if (teardown === void 0) found.push(`${name} needs the phase "tear down dependencies" to close`);
|
|
680
|
-
else if (ms(knobs.close_bound) >= valueIn(teardown.cap, "ms")) found.push(`${name}'s close_bound of ${ms(knobs.close_bound)} ms does not fit in the cap of "tear down dependencies"`);
|
|
681
|
-
for (const check of description.checks) {
|
|
682
|
-
if (check.dependency !== name) continue;
|
|
683
|
-
const slowest = valueIn(check.poll.timeout, "ms");
|
|
684
|
-
if (check.step === "round-trip" && requestTimeout > slowest + valueIn(check.poll.interval, "ms") * (1 + check.poll.jitter)) found.push(`${name}'s request_timeout outlives one jittered poll of ${check.name} (B7, O13)`);
|
|
685
|
-
}
|
|
686
|
-
return found;
|
|
687
|
-
}
|
|
468
|
+
attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),
|
|
469
|
+
problems: (description) => clientProblems(name, description, clientBounds(knobs))
|
|
688
470
|
};
|
|
689
471
|
};
|
|
690
472
|
//#endregion
|
|
@@ -842,19 +624,6 @@ const replyOf = (limiter, reply) => {
|
|
|
842
624
|
};
|
|
843
625
|
//#endregion
|
|
844
626
|
//#region src/redis/machine.ts
|
|
845
|
-
const t = (id, from, on, to, extra = {}) => ({
|
|
846
|
-
id,
|
|
847
|
-
from,
|
|
848
|
-
on,
|
|
849
|
-
to,
|
|
850
|
-
...extra
|
|
851
|
-
});
|
|
852
|
-
const knob = (key) => ({
|
|
853
|
-
key,
|
|
854
|
-
scope: "client",
|
|
855
|
-
unit: "s",
|
|
856
|
-
default: null
|
|
857
|
-
});
|
|
858
627
|
/** The states where the connection exists or tries to exist, thus where it can close. */
|
|
859
628
|
const LIVE = [
|
|
860
629
|
"connecting",
|
|
@@ -932,10 +701,10 @@ const REDIS_CLIENT_MACHINE = {
|
|
|
932
701
|
}
|
|
933
702
|
},
|
|
934
703
|
knobs: {
|
|
935
|
-
connect_bound:
|
|
936
|
-
driver_writeoff_time:
|
|
937
|
-
heartbeat_interval:
|
|
938
|
-
close_bound:
|
|
704
|
+
connect_bound: clientKnob("connectTimeout"),
|
|
705
|
+
driver_writeoff_time: clientKnob("socketTimeout"),
|
|
706
|
+
heartbeat_interval: clientKnob("watchdog.interval"),
|
|
707
|
+
close_bound: clientKnob("close.bound")
|
|
939
708
|
},
|
|
940
709
|
timers: {
|
|
941
710
|
connect_timeout: {
|
|
@@ -968,41 +737,33 @@ const REDIS_CLIENT_MACHINE = {
|
|
|
968
737
|
fires: "heartbeat",
|
|
969
738
|
note: "a PING on the connection of the application"
|
|
970
739
|
},
|
|
971
|
-
close:
|
|
972
|
-
duration: {
|
|
973
|
-
knob: "close_bound",
|
|
974
|
-
unit: "s"
|
|
975
|
-
},
|
|
976
|
-
arms: ["close.request"],
|
|
977
|
-
cancels: ["status.end"],
|
|
978
|
-
fires: "close.bound"
|
|
979
|
-
}
|
|
740
|
+
close: closeTimer()
|
|
980
741
|
},
|
|
981
742
|
transitions: [
|
|
982
|
-
|
|
743
|
+
transition("connect", [
|
|
983
744
|
"none",
|
|
984
745
|
"reconnecting",
|
|
985
746
|
"end",
|
|
986
747
|
"close"
|
|
987
748
|
], "status.connecting", "connecting"),
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
...ANY.map((state) =>
|
|
999
|
-
...ANY.map((state) =>
|
|
1000
|
-
...ANY.map((state) =>
|
|
1001
|
-
...ANY.map((state) =>
|
|
1002
|
-
...ANY.map((state) =>
|
|
749
|
+
transition("tcp", "connecting", "status.connect", "connect"),
|
|
750
|
+
transition("ready", "connect", "status.ready", "ready"),
|
|
751
|
+
transition("close", [...LIVE], "status.close", "close"),
|
|
752
|
+
transition("retry", "close", "status.reconnecting", "reconnecting"),
|
|
753
|
+
transition("end", [...LIVE, "close"], "status.end", "end"),
|
|
754
|
+
transition("join", "ready", "join", "ready"),
|
|
755
|
+
transition("miss", "ready", "watchdog.miss", "ready"),
|
|
756
|
+
transition("watchdog-reconnect", "ready", "watchdog.reconnect", "ready"),
|
|
757
|
+
transition("revive", "end", "watchdog.revive", "end"),
|
|
758
|
+
transition("window-ended", "close", "retry.window-ended", "close"),
|
|
759
|
+
...ANY.map((state) => transition(`error-${state}`, state, "error", state)),
|
|
760
|
+
...ANY.map((state) => transition(`connect-failed-${state}`, state, "connect.failed", state)),
|
|
761
|
+
...ANY.map((state) => transition(`reissue-${state}`, state, "command.reissue", state)),
|
|
762
|
+
...ANY.map((state) => transition(`close-bound-${state}`, state, "close.bound", state, { timer: "close" })),
|
|
763
|
+
...ANY.map((state) => transition(`close-destroy-${state}`, state, "close.destroy", state))
|
|
1003
764
|
]
|
|
1004
765
|
};
|
|
1005
766
|
//#endregion
|
|
1006
|
-
export { LIMITER_SCRIPT, REDIS_CLIENT_MACHINE, REDIS_OPERATIONS, commandName, firstDetector, heartbeatDetection, ioredis, meetings, redisClient, redisLimiterStore, retryBackoff, specProblems, tcpDial, unwatched };
|
|
767
|
+
export { LIMITER_SCRIPT, MANIFESTATIONS, REDIS_CLIENT_MACHINE, REDIS_OPERATIONS, commandName, firstDetector, heartbeatDetection, ioredis, meetings, redisClient, redisLimiterStore, retryBackoff, specProblems, tcpDial, unwatched };
|
|
1007
768
|
|
|
1008
769
|
//# sourceMappingURL=redis.js.map
|