@winkjs/composer 0.5.1 → 0.7.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 +1 -1
- package/package.json +3 -3
- package/src/composer.js +1 -1
- package/src/core/batch/aoo-encoder.js +4 -4
- package/src/core/batch/sliding-window.js +2 -2
- package/src/core/codec/index.js +4 -4
- package/src/core/emitter-manager/mqtt/connection.js +209 -0
- package/src/core/emitter-manager/mqtt/constants.js +43 -29
- package/src/core/emitter-manager/mqtt/emitter.js +108 -633
- package/src/core/emitter-manager/mqtt/health.js +151 -0
- package/src/core/emitter-manager/mqtt/index.js +68 -4
- package/src/core/emitter-manager/mqtt/publish.js +292 -0
- package/src/core/emitter-manager/mqtt/resolve-config.js +312 -0
- package/src/core/emitter-manager/mqtt/shutdown-drain.js +247 -0
- package/src/core/emitter-manager/terminal/assert-columns.js +1 -1
- package/src/core/emitter-manager/terminal/index.js +20 -5
- package/src/core/env-vars.js +143 -16
- package/src/core/logger/index.js +174 -0
- package/src/core/mqtt-protocol.js +2 -2
- package/src/core/partition-manager/get-stats.js +31 -0
- package/src/core/partition-manager/index.js +1 -0
- package/src/core/partition-manager/init.js +15 -0
- package/src/core/partition-manager/update.js +143 -87
- package/src/core/partition-manager/validate-flow.js +1 -1
- package/src/core/semantics/loader.js +1 -1
- package/src/core/semantics/warnings.js +5 -3
- package/src/core/shutdown-manager/index.js +71 -21
- package/src/core/source-manager/csv/start.js +109 -35
- package/src/core/source-manager/mqtt/client.js +276 -83
- package/src/core/source-manager/mqtt/dedup.js +19 -14
- package/src/core/source-manager/mqtt/index.js +33 -6
- package/src/core/source-manager/mqtt/status.js +211 -46
- package/src/core/source-manager/record-shape.js +55 -0
- package/src/core/source-manager/test-harness/field-generator.js +1 -1
- package/src/core/source-manager/test-harness/start.js +52 -10
- package/src/core/source-manager/test-harness/validate.js +1 -1
- package/src/core/storage-manager/questdb/address-policy.js +264 -0
- package/src/core/storage-manager/questdb/assert-columns.js +2 -2
- package/src/core/storage-manager/questdb/delivery-gate.js +218 -0
- package/src/core/storage-manager/questdb/ensure-tables.js +1 -1
- package/src/core/storage-manager/questdb/flush-engine.js +852 -0
- package/src/core/storage-manager/questdb/flush-tracker.js +331 -0
- package/src/core/storage-manager/questdb/index.js +592 -827
- package/src/core/storage-manager/questdb/persist-plan.js +147 -162
- package/src/core/storage-manager/questdb/resolve-options.js +403 -0
- package/src/core/storage-manager/questdb/shutdown-drain.js +181 -0
- package/src/core/storage-manager/questdb/skip-warnings.js +180 -0
- package/src/core/test-utils/tcp-proxy.js +194 -1
- package/src/core/tunable/helpers.js +1 -1
- package/src/core/utils/address/index.js +362 -0
- package/src/core/utils/address/probe.js +210 -0
- package/src/core/utils/callback-guard/index.js +176 -0
- package/src/core/utils/clock/index.js +34 -0
- package/src/core/utils/half-life/index.js +4 -4
- package/src/core/utils/jitter/index.js +51 -0
- package/src/core/utils/line-rate/index.js +104 -0
- package/src/core/utils/quantize/index.js +43 -19
- package/src/core/utils/validate/composer-validators.js +2 -2
- package/src/core/utils/validate/index.js +1 -1
- package/src/core/utils/validate/validate.js +1 -1
- package/src/core/wiring/assert-handle.js +5 -5
- package/src/core/wiring/assert-module.js +2 -2
- package/src/core/wiring/wire-emitters.js +7 -4
- package/src/core/wiring/wire-linear-graph.js +1 -1
- package/src/core/wiring/wire-node.js +6 -6
- package/src/core/wiring/wire-semantics.js +1 -1
- package/src/core/wiring/wire-storages.js +14 -5
- package/src/flow/driver.js +31 -6
- package/src/flow/expand-single-input-array.js +4 -4
- package/src/flow/flow-api-schemas.js +4 -4
- package/src/flow/flow.js +62 -41
- package/src/flow/for-each.js +12 -11
- package/src/flow/get-target-array.js +2 -2
- package/src/flow/load-node-module.js +1 -1
- package/src/flow/make-node-method.js +4 -4
- package/src/flow/resolve-pick-by-field.js +1 -1
- package/src/flow/run.js +200 -40
- package/src/flow/serialize.js +1 -1
- package/src/flow/spec-builder.js +1 -1
- package/src/flow/validate.js +1 -1
- package/src/nodes/butterworth-filter/init.js +5 -4
- package/src/nodes/categorize/update.js +3 -1
- package/src/nodes/controller/update.js +2 -1
- package/src/nodes/dwell-time-tracker/update.js +2 -1
- package/src/nodes/emit-if/helpers.js +57 -11
- package/src/nodes/emit-if/init.js +1 -1
- package/src/nodes/emit-if/update.js +8 -11
- package/src/nodes/page-hinkley/update.js +2 -1
- package/src/nodes/pass-if/update.js +3 -1
- package/src/nodes/persist-if/helpers.js +60 -4
- package/src/nodes/persist-if/init.js +1 -1
- package/src/nodes/persist-if/update.js +8 -16
- package/src/nodes/persistence-check/update.js +2 -1
- package/src/nodes/sanitize/update.js +4 -2
- package/src/nodes/swing-watch/update.js +3 -2
- package/src/nodes/threshold/update.js +2 -1
- package/src/nodes/trend/compute-confidence.js +4 -4
- package/src/nodes/trend/update.js +2 -1
- package/src/nodes/winnow/update.js +3 -1
- package/src/tools/training/features.js +13 -13
- package/src/tools/training/logistic.js +1 -1
- package/src/tools/training/metrics.js +2 -2
- package/src/tools/training/scale.js +2 -2
- package/src/tools/training/validate.js +7 -7
- package/src/core/utils/console-formatter/index.js +0 -207
- package/src/nodes/archive/csv/index.js +0 -6
- package/src/nodes/archive/csv/init.js +0 -36
- package/src/nodes/archive/csv/load.js +0 -70
- package/src/nodes/archive/csv/reset.js +0 -14
- package/src/nodes/archive/csv/simple-parser.js +0 -216
- package/src/nodes/archive/csv/test.js +0 -173
- package/src/nodes/archive/csv/update.js +0 -63
- package/src/nodes/sse-emitter/index.js +0 -7
- package/src/nodes/sse-emitter/init.js +0 -105
- package/src/nodes/sse-emitter/introspect.js +0 -134
- package/src/nodes/sse-emitter/publish-to.js +0 -6
- package/src/nodes/sse-emitter/recompute.js +0 -16
- package/src/nodes/sse-emitter/reset.js +0 -19
- package/src/nodes/sse-emitter/update.js +0 -69
- package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +0 -36
- package/src/nodes/sse-emitter/utils/build-message.js +0 -31
- package/src/nodes/sse-emitter/utils/connection-manager.js +0 -45
- package/src/nodes/sse-emitter/utils/endpoint-initializer.js +0 -20
- package/src/nodes/sse-emitter/utils/health-monitor.js +0 -38
- package/src/nodes/sse-emitter/utils/qos-handler.js +0 -41
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// core/storage-manager/questdb/flush-tracker.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview The ledger of QuestDB flushes in flight, and the
|
|
5
|
+
* deadline on each one (ADR-029).
|
|
6
|
+
*
|
|
7
|
+
* The client copies the rows out of its buffer the moment a flush
|
|
8
|
+
* starts, before any network work. From that moment the rows are
|
|
9
|
+
* neither in the buffer nor confirmed. This module counts them. Every
|
|
10
|
+
* flush the adapter starts passes through `track`, so `inFlightRows`
|
|
11
|
+
* is exact: rows inside flushes that have not settled. The engine adds
|
|
12
|
+
* it to the buffered count for pressure, and the shutdown drain walks
|
|
13
|
+
* `entries` to settle every flush it owes.
|
|
14
|
+
*
|
|
15
|
+
* Why a deadline. A send against an unreachable server can retry
|
|
16
|
+
* without end (the client's undici transport, a 4.2.0 fact recorded
|
|
17
|
+
* in `flush-engine.js`), so a flush promise may never settle. Each
|
|
18
|
+
* tracked flush therefore gets one timer. The deadline comes from
|
|
19
|
+
* `flushDeadlineFor`: the client's own bound for a batch of that many
|
|
20
|
+
* rows plus a margin, or the fixed `flushDeadlineMs` when the operator
|
|
21
|
+
* set one. A flush past its deadline is abandoned. Its rows leave the
|
|
22
|
+
* count, `abandonedFlushes` goes up by one, and the owner is told with
|
|
23
|
+
* `abandoned: true`. The rows are reported lost. They may still land
|
|
24
|
+
* if the server recovers, so that count is a floor on uncertainty, not
|
|
25
|
+
* a proof of loss.
|
|
26
|
+
*
|
|
27
|
+
* Settles exactly once. An entry settles when the client's promise
|
|
28
|
+
* resolves, when it rejects, or when the deadline fires, whichever
|
|
29
|
+
* comes first. The `settled` flag makes every later event inert: a
|
|
30
|
+
* late result from an abandoned flush moves no counter and calls no
|
|
31
|
+
* handler. The deadline timer is cleared when the flush settles in
|
|
32
|
+
* time, so a healthy adapter carries one live timer per flush in
|
|
33
|
+
* flight and none per row.
|
|
34
|
+
*
|
|
35
|
+
* The owner's handler. `track( flushPromise, rows, onDone )` calls
|
|
36
|
+
* `onDone( err, abandoned )` once: `err` is null when delivered, the
|
|
37
|
+
* client's rejection when it failed, or the abandonment error when the
|
|
38
|
+
* deadline passed. Loss reporting stays with the owner: the engine
|
|
39
|
+
* reports through its failure handler, an explicit `flush()` rejects to
|
|
40
|
+
* its caller, and the shutdown drain tallies. The drain wraps
|
|
41
|
+
* `entry.onDone` to add its tally, so the field is read at settle time,
|
|
42
|
+
* not captured at track time.
|
|
43
|
+
*
|
|
44
|
+
* Outcomes for health. Every settle also records its outcome, so
|
|
45
|
+
* health can read delivery and not only buffering. A delivered flush
|
|
46
|
+
* zeroes `consecutiveFlushFailures` and stamps `lastFlushAt`. A failed
|
|
47
|
+
* or abandoned flush adds one to the count and replaces
|
|
48
|
+
* `lastFlushError` with `{ message, abandoned, at }`. The last error is
|
|
49
|
+
* never cleared: the count says whether it is current, and the message
|
|
50
|
+
* stays readable after recovery. `deliveryStateOf` turns these into
|
|
51
|
+
* the health ladder (yellow on the first failure, red on the second or
|
|
52
|
+
* on an abandonment, green on the next delivered flush). The engine's
|
|
53
|
+
* `getHealth()` reads that one function too.
|
|
54
|
+
*
|
|
55
|
+
* The pause is red as well. The delivery gate tells the ledger when it
|
|
56
|
+
* pauses and resumes delivery, and the ledger mirrors the pause instant
|
|
57
|
+
* in `pausedSince`. The ladder reads red while it is set, whatever the
|
|
58
|
+
* failure count says, because the gate holds new rows until a probe
|
|
59
|
+
* passes. While the gate runs, only its resume clears the mirror. An
|
|
60
|
+
* explicit or recovery flush can land during a pause, and the ladder
|
|
61
|
+
* stays red then. At shutdown the gate is out of the loop, so a
|
|
62
|
+
* landing clears the mirror and the restored line prints beside the
|
|
63
|
+
* clean stop.
|
|
64
|
+
*
|
|
65
|
+
* Edges print once. Every step up the ladder, and every return to
|
|
66
|
+
* green, prints one line through the logger facade, with or without
|
|
67
|
+
* an `onDeliveryFailure` handler, because the callback serves programs
|
|
68
|
+
* and a line serves people. Entering yellow prints at warn, entering
|
|
69
|
+
* red at error, and returning to green at warn with the episode length
|
|
70
|
+
* and the rows reported lost in it. The red line names its cause: the
|
|
71
|
+
* count of failed flushes, or the pause after that count. It prints at
|
|
72
|
+
* the pause, before the gate's own `CIRCUIT_OPEN` line, and a later
|
|
73
|
+
* failed flush adds no second red line. A resume that steps down to
|
|
74
|
+
* yellow prints nothing. The restored line comes when the catch-up
|
|
75
|
+
* flush lands. A resume that lands on green, because a flush landed
|
|
76
|
+
* during the pause, prints the restored line after the gate's resumed
|
|
77
|
+
* line. Nothing prints while a state persists, however long an outage
|
|
78
|
+
* lasts. The token is
|
|
79
|
+
* `DELIVERY_HEALTH` (a console token, like `CIRCUIT_OPEN`, not an
|
|
80
|
+
* `err.code`).
|
|
81
|
+
*
|
|
82
|
+
* Nothing here runs on the per-row path. One entry object, one timer,
|
|
83
|
+
* and two closures per flush; a flush carries thousands of rows by
|
|
84
|
+
* default. The error record and the edge lines allocate only on the
|
|
85
|
+
* failure path, once per flush at most.
|
|
86
|
+
*
|
|
87
|
+
* @see ADR-029
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
import { logger } from '../../logger/index.js';
|
|
91
|
+
import { flushDeadlineFor } from './resolve-options.js';
|
|
92
|
+
import { monotonicNow } from '../../utils/clock/index.js';
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Consecutive failed flushes that read as red delivery. One failure is
|
|
96
|
+
* yellow, because the client's own retries make a single failure
|
|
97
|
+
* worth a look but not an alarm. Two in a row mean the server keeps
|
|
98
|
+
* refusing. An abandoned flush reads red on its own, because a
|
|
99
|
+
* deadline is already the client's full retry budget plus a margin.
|
|
100
|
+
*/
|
|
101
|
+
const HEALTH_FLUSH_FAILURE_RED_THRESHOLD = 2;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The delivery state the ledger's outcome fields spell: the ladder.
|
|
105
|
+
* One function for both readers, the edge detector in this module and
|
|
106
|
+
* the engine's `getHealth()`, so a line and a health read never
|
|
107
|
+
* disagree.
|
|
108
|
+
*
|
|
109
|
+
* @param {{consecutiveFlushFailures: number, lastFlushError: {abandoned: boolean}|null, pausedSince?: number|null}} ledger - The outcome fields
|
|
110
|
+
* @returns {'green'|'yellow'|'red'} The delivery state
|
|
111
|
+
*/
|
|
112
|
+
const deliveryStateOf = function ( ledger ) {
|
|
113
|
+
// A paused delivery is red whatever the count says: nothing lands
|
|
114
|
+
// until a probe passes (ADR-029 hold and probe). Health objects
|
|
115
|
+
// carry the same field, so a health read spells the same state.
|
|
116
|
+
const paused = ( ledger.pausedSince !== undefined ) && ( ledger.pausedSince !== null );
|
|
117
|
+
if ( paused ) {
|
|
118
|
+
return 'red';
|
|
119
|
+
}
|
|
120
|
+
const failures = ledger.consecutiveFlushFailures;
|
|
121
|
+
if ( failures === 0 ) {
|
|
122
|
+
return 'green';
|
|
123
|
+
}
|
|
124
|
+
if ( ( failures >= HEALTH_FLUSH_FAILURE_RED_THRESHOLD ) || ledger.lastFlushError.abandoned ) {
|
|
125
|
+
return 'red';
|
|
126
|
+
}
|
|
127
|
+
return 'yellow';
|
|
128
|
+
}; // deliveryStateOf()
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The error an abandoned flush carries: to the caller of an explicit
|
|
132
|
+
* `flush()`, to `onDeliveryFailure`, and as `cause` on a lossy shutdown.
|
|
133
|
+
*
|
|
134
|
+
* @param {number} rows - Rows the flush carried
|
|
135
|
+
* @param {number} deadlineMs - The deadline that passed
|
|
136
|
+
* @returns {Error} Classified DELIVERY_FAILED
|
|
137
|
+
*/
|
|
138
|
+
const abandonmentError = function ( rows, deadlineMs ) {
|
|
139
|
+
const err = new Error(
|
|
140
|
+
`winkComposer/questdb: flush abandoned, ${rows} row(s) lost [DELIVERY_FAILED]: no answer within ${deadlineMs} ms`
|
|
141
|
+
);
|
|
142
|
+
err.code = 'DELIVERY_FAILED';
|
|
143
|
+
return err;
|
|
144
|
+
}; // abandonmentError()
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Builds the ledger. The engine reads `ledger.inFlightRows` on the
|
|
148
|
+
* write path and the outcome fields in health; the drain walks
|
|
149
|
+
* `ledger.entries`. Only `track` writes to them.
|
|
150
|
+
*
|
|
151
|
+
* @param {Object} settings - Resolved settings; the deadline inputs (`flushDeadlineMs`, `retryTimeout`)
|
|
152
|
+
* @param {function} isShuttingDown - `() => boolean`, the engine's shutdown flag; a landing clears the pause mirror only then
|
|
153
|
+
* @returns {{ledger: {inFlightRows: number, abandonedFlushes: number, consecutiveFlushFailures: number, lastFlushAt: number|null, lastFlushError: {message: string, abandoned: boolean, at: number}|null, entries: Set}, track: function}}
|
|
154
|
+
*/
|
|
155
|
+
const createFlushTracker = function ( settings, isShuttingDown ) {
|
|
156
|
+
const ledger = {
|
|
157
|
+
inFlightRows: 0,
|
|
158
|
+
abandonedFlushes: 0,
|
|
159
|
+
consecutiveFlushFailures: 0,
|
|
160
|
+
lastFlushAt: null,
|
|
161
|
+
lastFlushError: null,
|
|
162
|
+
// The gate's pause instant, mirrored here so the ladder sees it.
|
|
163
|
+
pausedSince: null,
|
|
164
|
+
entries: new Set()
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// The episode behind the restored line: when delivery left green,
|
|
168
|
+
// on the stopwatch so a step in the wall clock cannot change the
|
|
169
|
+
// length (ADR-018), and the rows reported lost since. Both reset
|
|
170
|
+
// when green returns.
|
|
171
|
+
let episodeStartedAt = null;
|
|
172
|
+
let episodeRowsLost = 0;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Prints the one line an edge of the ladder earns. The restored
|
|
176
|
+
* line names the episode. The other two carry the failure's own
|
|
177
|
+
* message, so an abandonment reads as a chain in the ADR-028 grammar.
|
|
178
|
+
*
|
|
179
|
+
* @param {'green'|'yellow'|'red'} after - The state just entered
|
|
180
|
+
* @param {Error|null} err - The failure that caused the edge, null on restore
|
|
181
|
+
*/
|
|
182
|
+
const printEdge = function ( after, err ) {
|
|
183
|
+
if ( after === 'yellow' ) {
|
|
184
|
+
logger.warn( `winkComposer/questdb: delivery degraded, 1 flush failed [DELIVERY_HEALTH]: ${err.message}` );
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if ( after === 'red' ) {
|
|
188
|
+
// Red by the count, or red because the gate paused delivery
|
|
189
|
+
// after the failures so far.
|
|
190
|
+
const cause = ( ledger.pausedSince === null ) ?
|
|
191
|
+
` after ${ledger.consecutiveFlushFailures} failed flush(es)` :
|
|
192
|
+
`, paused after ${ledger.consecutiveFlushFailures} failed flush(es)`;
|
|
193
|
+
logger.error( `winkComposer/questdb: delivery red${cause} [DELIVERY_HEALTH]: ${err.message}` );
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const seconds = Math.round( ( monotonicNow() - episodeStartedAt ) / 1000 );
|
|
197
|
+
logger.warn(
|
|
198
|
+
`winkComposer/questdb: delivery restored after ${seconds} s, ${episodeRowsLost} row(s) reported lost meanwhile [DELIVERY_HEALTH]`
|
|
199
|
+
);
|
|
200
|
+
}; // printEdge()
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Steps the ladder from `before` to the state the ledger reads now,
|
|
204
|
+
* and prints the edge line when the two differ. Every edge site
|
|
205
|
+
* calls it: a settled flush, the gate's pause, the gate's resume.
|
|
206
|
+
* The episode starts on leaving green and ends on returning to it.
|
|
207
|
+
*
|
|
208
|
+
* @param {'green'|'yellow'|'red'} before - The state before the change
|
|
209
|
+
* @param {Error|null} err - The failure behind the edge, null on a landing or resume
|
|
210
|
+
*/
|
|
211
|
+
const stepLadder = function ( before, err ) {
|
|
212
|
+
const after = deliveryStateOf( ledger );
|
|
213
|
+
if ( after === before ) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if ( before === 'green' ) {
|
|
217
|
+
episodeStartedAt = monotonicNow();
|
|
218
|
+
}
|
|
219
|
+
printEdge( after, err );
|
|
220
|
+
if ( after === 'green' ) {
|
|
221
|
+
episodeStartedAt = null;
|
|
222
|
+
episodeRowsLost = 0;
|
|
223
|
+
}
|
|
224
|
+
}; // stepLadder()
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Records one settled flush for health and prints the edge line
|
|
228
|
+
* when the ladder changed. Runs once per flush, never per row.
|
|
229
|
+
*
|
|
230
|
+
* @param {Error|null} err - Null when delivered
|
|
231
|
+
* @param {boolean} abandoned - Whether the deadline ended it
|
|
232
|
+
* @param {number} rows - Rows the flush carried
|
|
233
|
+
*/
|
|
234
|
+
const recordOutcome = function ( err, abandoned, rows ) {
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
const before = deliveryStateOf( ledger );
|
|
237
|
+
if ( err === null ) {
|
|
238
|
+
ledger.consecutiveFlushFailures = 0;
|
|
239
|
+
ledger.lastFlushAt = now;
|
|
240
|
+
// While the gate runs, it owns the mirror. An explicit or
|
|
241
|
+
// recovery flush can land during a pause, and the ladder
|
|
242
|
+
// stays red until the gate's probe passes. At shutdown the
|
|
243
|
+
// gate is out of the loop, so a landing clears the mirror
|
|
244
|
+
// and the restored line prints beside the clean stop.
|
|
245
|
+
if ( isShuttingDown() ) {
|
|
246
|
+
ledger.pausedSince = null;
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
ledger.consecutiveFlushFailures += 1;
|
|
250
|
+
ledger.lastFlushError = { message: err.message, abandoned, at: now };
|
|
251
|
+
episodeRowsLost += rows;
|
|
252
|
+
}
|
|
253
|
+
stepLadder( before, err );
|
|
254
|
+
}; // recordOutcome()
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Records the gate's pause in the ledger and prints the red edge
|
|
258
|
+
* when the pause is what turned the ladder red. A pause from green
|
|
259
|
+
* happens when a flush landed between the failed flush and the
|
|
260
|
+
* probe's result. The episode then starts at the pause.
|
|
261
|
+
*
|
|
262
|
+
* @param {number} pausedSince - The gate's pause instant
|
|
263
|
+
* @param {string} finding - The probe's operator text
|
|
264
|
+
*/
|
|
265
|
+
const recordPause = function ( pausedSince, finding ) {
|
|
266
|
+
const before = deliveryStateOf( ledger );
|
|
267
|
+
ledger.pausedSince = pausedSince;
|
|
268
|
+
stepLadder( before, { message: finding } );
|
|
269
|
+
}; // recordPause()
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Records the gate's resume in the ledger. Red to yellow prints
|
|
273
|
+
* nothing: one failure stays on the count until a flush lands, and
|
|
274
|
+
* the restored line comes then. Red to green happens when a flush
|
|
275
|
+
* landed during the pause, and the restored line prints here, after
|
|
276
|
+
* the gate's resumed line.
|
|
277
|
+
*/
|
|
278
|
+
const recordResume = function () {
|
|
279
|
+
ledger.pausedSince = null;
|
|
280
|
+
if ( deliveryStateOf( ledger ) === 'green' ) {
|
|
281
|
+
stepLadder( 'red', null );
|
|
282
|
+
}
|
|
283
|
+
}; // recordResume()
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Registers a flush the moment it is called and arms its deadline.
|
|
287
|
+
*
|
|
288
|
+
* @param {Promise} flushPromise - The just-fired `sender.flush()`
|
|
289
|
+
* @param {number} rows - Rows the flush copy carries
|
|
290
|
+
* @param {function} onDone - `( err|null, abandoned )`, called exactly once
|
|
291
|
+
* @returns {{rows: number, onDone: function, settled: boolean, timer: Object}} The tracked entry
|
|
292
|
+
*/
|
|
293
|
+
const track = function ( flushPromise, rows, onDone ) {
|
|
294
|
+
const entry = { rows, onDone, settled: false, timer: null };
|
|
295
|
+
ledger.inFlightRows += rows;
|
|
296
|
+
ledger.entries.add( entry );
|
|
297
|
+
|
|
298
|
+
const finish = function ( err, abandoned ) {
|
|
299
|
+
if ( entry.settled ) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
entry.settled = true;
|
|
303
|
+
clearTimeout( entry.timer );
|
|
304
|
+
ledger.entries.delete( entry );
|
|
305
|
+
ledger.inFlightRows -= rows;
|
|
306
|
+
recordOutcome( err, abandoned, rows );
|
|
307
|
+
entry.onDone( err, abandoned );
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const deadlineMs = flushDeadlineFor( rows, settings );
|
|
311
|
+
entry.timer = setTimeout( function () {
|
|
312
|
+
ledger.abandonedFlushes += 1;
|
|
313
|
+
finish( abandonmentError( rows, deadlineMs ), true );
|
|
314
|
+
}, deadlineMs );
|
|
315
|
+
entry.timer.unref();
|
|
316
|
+
|
|
317
|
+
flushPromise.then(
|
|
318
|
+
function () {
|
|
319
|
+
finish( null, false );
|
|
320
|
+
},
|
|
321
|
+
function ( err ) {
|
|
322
|
+
finish( err, false );
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
return entry;
|
|
326
|
+
}; // track()
|
|
327
|
+
|
|
328
|
+
return { ledger, track, recordPause, recordResume };
|
|
329
|
+
}; // createFlushTracker()
|
|
330
|
+
|
|
331
|
+
export { createFlushTracker, deliveryStateOf };
|