@serfab/cadre-core 1.17.0 → 1.18.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 +3 -2
- package/dist/cadre-invite-protocol.d.ts +35 -13
- package/dist/cadre-invite-protocol.js +90 -25
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-node.d.ts +586 -255
- package/dist/cadre-node.js +1176 -564
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +66 -9
- package/dist/control-cohort.js +67 -9
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +451 -193
- package/dist/control-database.js +701 -359
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +6 -0
- package/dist/control-protocol-guard.js +14 -2
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +48 -31
- package/dist/control-schema-version.js +67 -45
- package/dist/control-schema-version.js.map +1 -1
- package/dist/control-schema.d.ts +8 -8
- package/dist/control-schema.js +16 -16
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +42 -7
- package/dist/control-write-retry.js +87 -19
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +14 -13
- package/dist/index.js +16 -13
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +26 -9
- package/dist/joined-strand-store.js +63 -24
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/member-redemption.d.ts +15 -9
- package/dist/member-redemption.js +13 -8
- package/dist/member-redemption.js.map +1 -1
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-grant-protocol.d.ts +11 -7
- package/dist/owner-grant-protocol.js +31 -17
- package/dist/owner-grant-protocol.js.map +1 -1
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +47 -40
- package/dist/seed-bootstrap.js +66 -61
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/strand-database.d.ts +23 -2
- package/dist/strand-database.js +26 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -89
- package/dist/strand-first-sync-gate.js +288 -122
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +26 -17
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +40 -28
- package/dist/strand-membership-writer.js +212 -153
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.js +5 -2
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +90 -49
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/cadre-invite-protocol.ts +119 -31
- package/src/cadre-node.ts +1282 -584
- package/src/control-cohort.ts +86 -9
- package/src/control-database.ts +848 -403
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +16 -2
- package/src/control-schema-version.ts +101 -51
- package/src/control-schema.ts +16 -16
- package/src/control-write-retry.ts +91 -19
- package/src/delegate-admission.ts +351 -351
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +30 -17
- package/src/joined-strand-store.ts +69 -24
- package/src/link-budget.ts +9 -7
- package/src/member-redemption.ts +20 -13
- package/src/membership-view.ts +144 -0
- package/src/owner-grant-protocol.ts +33 -18
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/seed-bootstrap.ts +72 -67
- package/src/strand-database.ts +352 -322
- package/src/strand-first-sync-gate.ts +334 -134
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +33 -25
- package/src/strand-membership-writer.ts +2093 -2019
- package/src/strand-move.ts +5 -2
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +89 -51
package/dist/strand-watcher.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import type { TableChangeWatch, TableChangeWatchOptions } from './table-change-watch.js';
|
|
1
2
|
import type { StrandFilter, StrandRow } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default backstop: the longest the watcher goes without re-reading when no change to
|
|
5
|
+
* `Strand` or `JoinedStrand` is heard (`docs/architecture.md` → "Table change subscriptions"
|
|
6
|
+
* says what the change subscription misses).
|
|
7
|
+
*
|
|
8
|
+
* NOTE: on a cadre larger than `CONTROL_REPLICATION_BREADTH` (16), a machine outside the
|
|
9
|
+
* `Strand` block's cohort hears no change event, so this backstop is its only path and a
|
|
10
|
+
* strand founded elsewhere reaches it up to 5 minutes later. If that matters, shorten the
|
|
11
|
+
* backstop for such machines rather than for everyone.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_STRAND_WATCH_BACKSTOP_MS: number;
|
|
2
14
|
/**
|
|
3
15
|
* Extended strand row that includes the sAppId for filtering purposes.
|
|
4
16
|
* The sAppId is provided by the hosting application, not from the control network.
|
|
@@ -15,10 +27,14 @@ export interface StrandWatcherCallbacks {
|
|
|
15
27
|
onStrandRemoved: (strandId: string) => Promise<void>;
|
|
16
28
|
}
|
|
17
29
|
/**
|
|
18
|
-
*
|
|
30
|
+
* What the watcher reads, and how it hears that what it reads changed.
|
|
19
31
|
*/
|
|
20
32
|
export interface StrandQueryable {
|
|
21
33
|
queryStrands(): Promise<StrandRow[]>;
|
|
34
|
+
/** Subscribe to changes of the tables {@link queryStrands} reads. Absent: runs come from `trigger` and the backstop only. */
|
|
35
|
+
watchChanges?(onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
|
|
36
|
+
/** True when the last {@link queryStrands} answered part of its result from a stand-in after a failed read. */
|
|
37
|
+
lastReadPartial?(): boolean;
|
|
22
38
|
}
|
|
23
39
|
/**
|
|
24
40
|
* Interface for looking up sAppId for a strand (for filtering)
|
|
@@ -31,11 +47,16 @@ export interface SAppIdLookup {
|
|
|
31
47
|
* Watches the control network's Strand table for changes and triggers
|
|
32
48
|
* strand instance start/stop via callbacks.
|
|
33
49
|
*
|
|
34
|
-
*
|
|
50
|
+
* Every read is one run on a {@link TableChangeWatch}: once at start, on a change to the
|
|
51
|
+
* tables the queryable reads, on the watcher's own retries, and on the backstop.
|
|
35
52
|
*/
|
|
36
53
|
export declare class StrandWatcher {
|
|
37
54
|
private readonly filter;
|
|
38
|
-
|
|
55
|
+
/** Longest gap between runs when no change is heard. */
|
|
56
|
+
private readonly backstopMs;
|
|
57
|
+
/** First step of the launch-retry and read-retry ladders. */
|
|
58
|
+
private readonly retryBaseMs;
|
|
59
|
+
private readonly readRetryCapMs;
|
|
39
60
|
private readonly callbacks;
|
|
40
61
|
private readonly queryable;
|
|
41
62
|
private readonly sAppIdLookup?;
|
|
@@ -48,7 +69,7 @@ export declare class StrandWatcher {
|
|
|
48
69
|
*/
|
|
49
70
|
private readonly now;
|
|
50
71
|
private knownStrands;
|
|
51
|
-
/** Ids admitted under a `defer` decision; re-evaluated each
|
|
72
|
+
/** Ids admitted under a `defer` decision; re-evaluated each run until they resolve. */
|
|
52
73
|
private provisional;
|
|
53
74
|
/** Ids whose last launch attempt threw, with the backoff gating their retry. */
|
|
54
75
|
private failureStates;
|
|
@@ -59,36 +80,69 @@ export declare class StrandWatcher {
|
|
|
59
80
|
* strand, the permanence has to be recorded explicitly.
|
|
60
81
|
*/
|
|
61
82
|
private suppressed;
|
|
62
|
-
|
|
63
|
-
private
|
|
83
|
+
/** Consecutive runs whose read failed or was partial; drives {@link scheduleReadRetry}. */
|
|
84
|
+
private readFailures;
|
|
85
|
+
/** Built by {@link start}, disposed by {@link stop}; a restart builds a new one. */
|
|
86
|
+
private watch;
|
|
64
87
|
private running;
|
|
65
|
-
|
|
88
|
+
/**
|
|
89
|
+
* @param backstopMs - `CadreNodeConfig.strandWatchInterval`: the longest the watcher goes
|
|
90
|
+
* without re-reading when no change is heard. Also caps the retry ladders' first step.
|
|
91
|
+
*/
|
|
92
|
+
constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, backstopMs?: number, sAppIdLookup?: SAppIdLookup, now?: () => number);
|
|
66
93
|
/**
|
|
67
94
|
* Evaluate a strand against the current filter, distinguishing a not-yet-known
|
|
68
95
|
* sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
|
|
69
|
-
* provisional and re-checked on subsequent
|
|
96
|
+
* provisional and re-checked on subsequent runs (see {@link sAppIdLearned}).
|
|
70
97
|
*/
|
|
71
98
|
private evaluateFilter;
|
|
72
99
|
/**
|
|
73
100
|
* Record a failed launch attempt and schedule when the strand may be retried:
|
|
74
|
-
* `
|
|
101
|
+
* `retryBaseMs * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
|
|
75
102
|
*/
|
|
76
103
|
private recordFailure;
|
|
77
104
|
/**
|
|
78
|
-
*
|
|
105
|
+
* One run: read, reconcile, then arm the follow-up runs this run needs. A run the watch
|
|
106
|
+
* starts clears every pending wake, so each run re-arms the retries still owed. A
|
|
107
|
+
* {@link forcePoll} run clears none, so what it arms may sit beside an equivalent wake;
|
|
108
|
+
* whichever fires first runs and clears the other.
|
|
109
|
+
*/
|
|
110
|
+
private runOnce;
|
|
111
|
+
/**
|
|
112
|
+
* Wake for the earliest launch retry falling due after `since`. A backoff that expired at
|
|
113
|
+
* or before `since` belongs to a strand the run starting then either attempted or skipped
|
|
114
|
+
* for a reason other than backoff (suppressed, filtered out); waking for it would spin.
|
|
115
|
+
* Without this a failed launch would wait for an unrelated change or the backstop.
|
|
116
|
+
*/
|
|
117
|
+
private scheduleLaunchRetry;
|
|
118
|
+
/**
|
|
119
|
+
* After a failed or partial read, run again on a ladder from `retryBaseMs`, doubling per
|
|
120
|
+
* consecutive failed run up to `readRetryCapMs`; a complete read resets it. Not left to
|
|
121
|
+
* the backstop because the plugin arms a table's replicated-change bridge on the table's
|
|
122
|
+
* first successful initialization: a machine whose first read failed (cut off from its
|
|
123
|
+
* party, or its sibling unreachable for a never-written `JoinedStrand`) would otherwise
|
|
124
|
+
* hear nothing until then.
|
|
125
|
+
*/
|
|
126
|
+
private scheduleReadRetry;
|
|
127
|
+
/**
|
|
128
|
+
* Read and reconcile. Resolves true when the read answered in full: false when it threw,
|
|
129
|
+
* or when the queryable answered part of it from a stand-in.
|
|
79
130
|
*/
|
|
80
131
|
private poll;
|
|
81
132
|
/**
|
|
82
133
|
* Start watching for strand changes
|
|
83
134
|
*/
|
|
84
135
|
start(): Promise<void>;
|
|
136
|
+
/** The watch every run goes through: the queryable's change subscription, or a timer-only one. */
|
|
137
|
+
private subscribe;
|
|
85
138
|
/**
|
|
86
139
|
* Stop watching for strand changes
|
|
87
140
|
*/
|
|
88
141
|
stop(): Promise<void>;
|
|
89
142
|
/**
|
|
90
|
-
* Forget a strand whose launch failed outside this watcher, so a later
|
|
91
|
-
* re-offers it — gated by the same backoff a watcher-driven failure gets
|
|
143
|
+
* Forget a strand whose launch failed outside this watcher, so a later run
|
|
144
|
+
* re-offers it — gated by the same backoff a watcher-driven failure gets, and woken
|
|
145
|
+
* when that backoff expires.
|
|
92
146
|
*
|
|
93
147
|
* A failed launch normally leaves nothing running (StrandInstanceManager drops the
|
|
94
148
|
* record), which is what makes re-offering it correct. One case does leave something
|
|
@@ -99,6 +153,16 @@ export declare class StrandWatcher {
|
|
|
99
153
|
* not read the line above as "nothing is running".
|
|
100
154
|
*/
|
|
101
155
|
forgetStrand(strandId: string): void;
|
|
156
|
+
/**
|
|
157
|
+
* Re-read soon: something the queryable merges in beside the watched tables changed
|
|
158
|
+
* (`CadreNode`'s machine-local joined-strand records). A no-op once stopped.
|
|
159
|
+
*/
|
|
160
|
+
refresh(): void;
|
|
161
|
+
/**
|
|
162
|
+
* The host just learned `strandId`'s sAppId: if the strand was admitted provisionally
|
|
163
|
+
* under an `sAppId` filter, run so the admission resolves now rather than on the backstop.
|
|
164
|
+
*/
|
|
165
|
+
sAppIdLearned(strandId: string): void;
|
|
102
166
|
/**
|
|
103
167
|
* Never offer this strand again this session. Two callers, both meaning "the retry
|
|
104
168
|
* ladder cannot help here": a deliberate local stop, and a launch that can never
|
|
@@ -129,7 +193,10 @@ export declare class StrandWatcher {
|
|
|
129
193
|
*/
|
|
130
194
|
getKnownStrands(): Map<string, StrandRow>;
|
|
131
195
|
/**
|
|
132
|
-
*
|
|
196
|
+
* Run once now, outside the watch's schedule (tests, and callers that just wrote a row
|
|
197
|
+
* and want this node to converge before returning). May overlap a scheduled run:
|
|
198
|
+
* `knownStrands` is updated before each callback's await, so neither launches nor
|
|
199
|
+
* removes a strand twice.
|
|
133
200
|
*/
|
|
134
201
|
forcePoll(): Promise<void>;
|
|
135
202
|
}
|
package/dist/strand-watcher.js
CHANGED
|
@@ -1,22 +1,48 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
+
import { scheduleRuns } from './table-change-watch.js';
|
|
2
3
|
const log = debug('sereus:cadre:strand-watcher');
|
|
3
4
|
/**
|
|
4
5
|
* Ceiling on the retry delay for a strand whose launch keeps failing. Attempts
|
|
5
6
|
* are never abandoned — the failure this backoff exists for (a transient network
|
|
6
7
|
* or storage fault) can last a long time — but a permanently-unlaunchable strand
|
|
7
|
-
* must not re-attempt on every
|
|
8
|
+
* must not re-attempt on every run and storm the host app with `strand:error`.
|
|
8
9
|
*/
|
|
9
10
|
const MAX_RETRY_BACKOFF_MS = 5 * 60 * 1000;
|
|
11
|
+
/**
|
|
12
|
+
* Default backstop: the longest the watcher goes without re-reading when no change to
|
|
13
|
+
* `Strand` or `JoinedStrand` is heard (`docs/architecture.md` → "Table change subscriptions"
|
|
14
|
+
* says what the change subscription misses).
|
|
15
|
+
*
|
|
16
|
+
* NOTE: on a cadre larger than `CONTROL_REPLICATION_BREADTH` (16), a machine outside the
|
|
17
|
+
* `Strand` block's cohort hears no change event, so this backstop is its only path and a
|
|
18
|
+
* strand founded elsewhere reaches it up to 5 minutes later. If that matters, shorten the
|
|
19
|
+
* backstop for such machines rather than for everyone.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_STRAND_WATCH_BACKSTOP_MS = 5 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* First step of the launch-retry and read-retry ladders, unless the backstop is shorter: a
|
|
24
|
+
* test or scenario that passes a short interval keeps the short base it always had.
|
|
25
|
+
*/
|
|
26
|
+
const LAUNCH_RETRY_BASE_MS = 5000;
|
|
27
|
+
/** Ceiling on the read-retry ladder (see `StrandWatcher.scheduleReadRetry`), unless the backstop is shorter. */
|
|
28
|
+
const READ_RETRY_CAP_MS = 60000;
|
|
29
|
+
/** Delay before the first run, so `start()` never reads synchronously. */
|
|
30
|
+
const INITIAL_RUN_DELAY_MS = 100;
|
|
10
31
|
/**
|
|
11
32
|
* Watches the control network's Strand table for changes and triggers
|
|
12
33
|
* strand instance start/stop via callbacks.
|
|
13
34
|
*
|
|
14
|
-
*
|
|
35
|
+
* Every read is one run on a {@link TableChangeWatch}: once at start, on a change to the
|
|
36
|
+
* tables the queryable reads, on the watcher's own retries, and on the backstop.
|
|
15
37
|
*/
|
|
16
38
|
export class StrandWatcher {
|
|
17
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @param backstopMs - `CadreNodeConfig.strandWatchInterval`: the longest the watcher goes
|
|
41
|
+
* without re-reading when no change is heard. Also caps the retry ladders' first step.
|
|
42
|
+
*/
|
|
43
|
+
constructor(queryable, callbacks, filter = { mode: 'all' }, backstopMs = DEFAULT_STRAND_WATCH_BACKSTOP_MS, sAppIdLookup, now = Date.now) {
|
|
18
44
|
this.knownStrands = new Map();
|
|
19
|
-
/** Ids admitted under a `defer` decision; re-evaluated each
|
|
45
|
+
/** Ids admitted under a `defer` decision; re-evaluated each run until they resolve. */
|
|
20
46
|
this.provisional = new Set();
|
|
21
47
|
/** Ids whose last launch attempt threw, with the backoff gating their retry. */
|
|
22
48
|
this.failureStates = new Map();
|
|
@@ -27,21 +53,25 @@ export class StrandWatcher {
|
|
|
27
53
|
* strand, the permanence has to be recorded explicitly.
|
|
28
54
|
*/
|
|
29
55
|
this.suppressed = new Set();
|
|
30
|
-
|
|
31
|
-
this.
|
|
56
|
+
/** Consecutive runs whose read failed or was partial; drives {@link scheduleReadRetry}. */
|
|
57
|
+
this.readFailures = 0;
|
|
58
|
+
/** Built by {@link start}, disposed by {@link stop}; a restart builds a new one. */
|
|
59
|
+
this.watch = null;
|
|
32
60
|
this.running = false;
|
|
33
61
|
this.queryable = queryable;
|
|
34
62
|
this.callbacks = callbacks;
|
|
35
63
|
this.filter = filter;
|
|
36
|
-
this.
|
|
64
|
+
this.backstopMs = backstopMs;
|
|
65
|
+
this.retryBaseMs = Math.min(backstopMs, LAUNCH_RETRY_BASE_MS);
|
|
66
|
+
this.readRetryCapMs = Math.min(backstopMs, READ_RETRY_CAP_MS);
|
|
37
67
|
this.sAppIdLookup = sAppIdLookup;
|
|
38
68
|
this.now = now;
|
|
39
|
-
log('StrandWatcher created with filter: %o,
|
|
69
|
+
log('StrandWatcher created with filter: %o, backstop: %dms', filter, backstopMs);
|
|
40
70
|
}
|
|
41
71
|
/**
|
|
42
72
|
* Evaluate a strand against the current filter, distinguishing a not-yet-known
|
|
43
73
|
* sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
|
|
44
|
-
* provisional and re-checked on subsequent
|
|
74
|
+
* provisional and re-checked on subsequent runs (see {@link sAppIdLearned}).
|
|
45
75
|
*/
|
|
46
76
|
evaluateFilter(strand) {
|
|
47
77
|
switch (this.filter.mode) {
|
|
@@ -58,7 +88,7 @@ export class StrandWatcher {
|
|
|
58
88
|
}
|
|
59
89
|
const sAppId = this.sAppIdLookup.getSAppId(strand.Id);
|
|
60
90
|
if (sAppId === undefined) {
|
|
61
|
-
// sAppId not yet known - admit provisionally and re-evaluate
|
|
91
|
+
// sAppId not yet known - admit provisionally and re-evaluate once it is learned.
|
|
62
92
|
log('sAppId unknown for strand %s - deferring filter decision', strand.Id);
|
|
63
93
|
return 'defer';
|
|
64
94
|
}
|
|
@@ -72,20 +102,70 @@ export class StrandWatcher {
|
|
|
72
102
|
}
|
|
73
103
|
/**
|
|
74
104
|
* Record a failed launch attempt and schedule when the strand may be retried:
|
|
75
|
-
* `
|
|
105
|
+
* `retryBaseMs * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
|
|
76
106
|
*/
|
|
77
107
|
recordFailure(strandId) {
|
|
78
108
|
const failures = (this.failureStates.get(strandId)?.failures ?? 0) + 1;
|
|
79
|
-
const delay = Math.min(this.
|
|
109
|
+
const delay = Math.min(this.retryBaseMs * 2 ** (failures - 1), MAX_RETRY_BACKOFF_MS);
|
|
80
110
|
this.failureStates.set(strandId, { failures, nextAttemptAt: this.now() + delay });
|
|
81
111
|
log('Strand %s launch failed (%d consecutive) - next attempt in %dms', strandId, failures, delay);
|
|
82
112
|
}
|
|
83
113
|
/**
|
|
84
|
-
*
|
|
114
|
+
* One run: read, reconcile, then arm the follow-up runs this run needs. A run the watch
|
|
115
|
+
* starts clears every pending wake, so each run re-arms the retries still owed. A
|
|
116
|
+
* {@link forcePoll} run clears none, so what it arms may sit beside an equivalent wake;
|
|
117
|
+
* whichever fires first runs and clears the other.
|
|
118
|
+
*/
|
|
119
|
+
async runOnce() {
|
|
120
|
+
const startedAt = this.now();
|
|
121
|
+
const readComplete = await this.poll();
|
|
122
|
+
if (this.running) {
|
|
123
|
+
this.scheduleLaunchRetry(startedAt);
|
|
124
|
+
this.scheduleReadRetry(readComplete);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Wake for the earliest launch retry falling due after `since`. A backoff that expired at
|
|
129
|
+
* or before `since` belongs to a strand the run starting then either attempted or skipped
|
|
130
|
+
* for a reason other than backoff (suppressed, filtered out); waking for it would spin.
|
|
131
|
+
* Without this a failed launch would wait for an unrelated change or the backstop.
|
|
132
|
+
*/
|
|
133
|
+
scheduleLaunchRetry(since) {
|
|
134
|
+
let earliest = Infinity;
|
|
135
|
+
for (const { nextAttemptAt } of this.failureStates.values()) {
|
|
136
|
+
if (nextAttemptAt > since) {
|
|
137
|
+
earliest = Math.min(earliest, nextAttemptAt);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (earliest !== Infinity) {
|
|
141
|
+
this.watch?.trigger(Math.max(0, earliest - this.now()));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* After a failed or partial read, run again on a ladder from `retryBaseMs`, doubling per
|
|
146
|
+
* consecutive failed run up to `readRetryCapMs`; a complete read resets it. Not left to
|
|
147
|
+
* the backstop because the plugin arms a table's replicated-change bridge on the table's
|
|
148
|
+
* first successful initialization: a machine whose first read failed (cut off from its
|
|
149
|
+
* party, or its sibling unreachable for a never-written `JoinedStrand`) would otherwise
|
|
150
|
+
* hear nothing until then.
|
|
151
|
+
*/
|
|
152
|
+
scheduleReadRetry(readComplete) {
|
|
153
|
+
if (readComplete) {
|
|
154
|
+
this.readFailures = 0;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.readFailures++;
|
|
158
|
+
const delay = Math.min(this.retryBaseMs * 2 ** (this.readFailures - 1), this.readRetryCapMs);
|
|
159
|
+
log('Strand read incomplete (%d consecutive) - re-reading in %dms', this.readFailures, delay);
|
|
160
|
+
this.watch?.trigger(delay);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Read and reconcile. Resolves true when the read answered in full: false when it threw,
|
|
164
|
+
* or when the queryable answered part of it from a stand-in.
|
|
85
165
|
*/
|
|
86
166
|
async poll() {
|
|
87
167
|
if (!this.running)
|
|
88
|
-
return;
|
|
168
|
+
return true;
|
|
89
169
|
try {
|
|
90
170
|
const currentStrands = await this.queryable.queryStrands();
|
|
91
171
|
const currentMap = new Map(currentStrands.map(s => [s.Id, s]));
|
|
@@ -96,7 +176,7 @@ export class StrandWatcher {
|
|
|
96
176
|
if (this.knownStrands.has(strand.Id))
|
|
97
177
|
continue;
|
|
98
178
|
const failure = this.failureStates.get(strand.Id);
|
|
99
|
-
// Clock read per candidate, not once per
|
|
179
|
+
// Clock read per candidate, not once per run: an earlier strand's launch
|
|
100
180
|
// may have taken a long time, and this one's gate must reflect that.
|
|
101
181
|
if (failure && this.now() < failure.nextAttemptAt)
|
|
102
182
|
continue; // backing off after a failed launch
|
|
@@ -104,11 +184,11 @@ export class StrandWatcher {
|
|
|
104
184
|
if (decision === 'reject')
|
|
105
185
|
continue;
|
|
106
186
|
log('Strand added: %s', strand.Id);
|
|
107
|
-
// Set BEFORE the await: this doubles as the in-flight guard that stops
|
|
108
|
-
//
|
|
187
|
+
// Set BEFORE the await: this doubles as the in-flight guard that stops a
|
|
188
|
+
// concurrent run (`forcePoll` beside a scheduled one) from launching the strand twice.
|
|
109
189
|
this.knownStrands.set(strand.Id, strand);
|
|
110
190
|
if (decision === 'defer') {
|
|
111
|
-
// Provisional admission: sAppId unknown, re-check on later
|
|
191
|
+
// Provisional admission: sAppId unknown, re-check on later runs.
|
|
112
192
|
this.provisional.add(strand.Id);
|
|
113
193
|
}
|
|
114
194
|
try {
|
|
@@ -143,7 +223,7 @@ export class StrandWatcher {
|
|
|
143
223
|
log('Error handling strand remove for %s: %o', strandId, error);
|
|
144
224
|
}
|
|
145
225
|
}
|
|
146
|
-
// decision === 'defer': still unknown, leave provisional
|
|
226
|
+
// decision === 'defer': still unknown, leave provisional until sAppIdLearned wakes a run.
|
|
147
227
|
}
|
|
148
228
|
// Find removed strands. A strand whose launch failed is no longer in
|
|
149
229
|
// knownStrands, so it correctly never fires onStrandRemoved — nothing was
|
|
@@ -169,8 +249,8 @@ export class StrandWatcher {
|
|
|
169
249
|
this.failureStates.delete(strandId);
|
|
170
250
|
}
|
|
171
251
|
}
|
|
172
|
-
// NOTE: the clear needs a
|
|
173
|
-
// unpublishes and re-publishes a locally-stopped id
|
|
252
|
+
// NOTE: the clear needs a run that actually SEES the row absent, so a sibling that
|
|
253
|
+
// unpublishes and re-publishes a locally-stopped id between two runs leaves
|
|
174
254
|
// it suppressed for the session. Contrived today — re-seating a removed id is
|
|
175
255
|
// owner-gated and manual — and there is no row version to distinguish the new row
|
|
176
256
|
// from the old. If re-publishing a removed id ever becomes routine, give the row a
|
|
@@ -180,15 +260,18 @@ export class StrandWatcher {
|
|
|
180
260
|
this.suppressed.delete(strandId);
|
|
181
261
|
}
|
|
182
262
|
}
|
|
263
|
+
return !(this.queryable.lastReadPartial?.() ?? false);
|
|
183
264
|
}
|
|
184
265
|
catch (error) {
|
|
185
266
|
// NOTE: a machine cut off from its party before it ever received the `Strand` block
|
|
186
|
-
// fails this read `cohort-unreachable` on every
|
|
187
|
-
// `control-cohort-edge-carries-data`). Harmless: nothing was ever
|
|
188
|
-
// this machine never held, and this catch keeps the known set. Do
|
|
189
|
-
// empty the way `ControlDatabase.queryRevokedStamps` does for
|
|
190
|
-
// answer here runs the removed-strand loop above. Revisit if
|
|
267
|
+
// fails this read `cohort-unreachable` on every step of the read-retry ladder and logs
|
|
268
|
+
// here (observed in `control-cohort-edge-carries-data`). Harmless: nothing was ever
|
|
269
|
+
// launched from a block this machine never held, and this catch keeps the known set. Do
|
|
270
|
+
// not answer it as empty the way `ControlDatabase.queryRevokedStamps` does for
|
|
271
|
+
// `Revocation` — an empty answer here runs the removed-strand loop above. Revisit if
|
|
272
|
+
// the log volume matters.
|
|
191
273
|
log('Error polling strands: %o', error);
|
|
274
|
+
return false;
|
|
192
275
|
}
|
|
193
276
|
}
|
|
194
277
|
/**
|
|
@@ -200,19 +283,21 @@ export class StrandWatcher {
|
|
|
200
283
|
return;
|
|
201
284
|
}
|
|
202
285
|
log('Starting StrandWatcher');
|
|
286
|
+
// Subscribed before the first read, so a commit landing between them is either seen by
|
|
287
|
+
// that read or wakes a follow-up run.
|
|
288
|
+
this.watch = this.subscribe();
|
|
203
289
|
this.running = true;
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}, this.pollInterval);
|
|
208
|
-
// Defer the first poll so start() doesn't block the caller.
|
|
209
|
-
// Strands added via addStrand() typically arrive after start() completes.
|
|
210
|
-
this.initialPollTimer = setTimeout(() => {
|
|
211
|
-
this.initialPollTimer = null;
|
|
212
|
-
void this.poll();
|
|
213
|
-
}, 100);
|
|
290
|
+
// Deferred so start() doesn't block the caller; strands added via addStrand()
|
|
291
|
+
// typically arrive after start() completes.
|
|
292
|
+
this.watch.trigger(INITIAL_RUN_DELAY_MS);
|
|
214
293
|
log('StrandWatcher started');
|
|
215
294
|
}
|
|
295
|
+
/** The watch every run goes through: the queryable's change subscription, or a timer-only one. */
|
|
296
|
+
subscribe() {
|
|
297
|
+
const onChange = () => this.runOnce();
|
|
298
|
+
const options = { label: 'strand-watcher', backstopMs: this.backstopMs };
|
|
299
|
+
return this.queryable.watchChanges?.(onChange, options) ?? scheduleRuns(onChange, options);
|
|
300
|
+
}
|
|
216
301
|
/**
|
|
217
302
|
* Stop watching for strand changes
|
|
218
303
|
*/
|
|
@@ -222,23 +307,20 @@ export class StrandWatcher {
|
|
|
222
307
|
}
|
|
223
308
|
log('Stopping StrandWatcher');
|
|
224
309
|
this.running = false;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
if (this.pollTimer) {
|
|
230
|
-
clearInterval(this.pollTimer);
|
|
231
|
-
this.pollTimer = null;
|
|
232
|
-
}
|
|
310
|
+
// A run already in flight finishes; `running` keeps it from arming anything further.
|
|
311
|
+
this.watch?.dispose();
|
|
312
|
+
this.watch = null;
|
|
233
313
|
this.knownStrands.clear();
|
|
234
314
|
this.provisional.clear();
|
|
235
315
|
this.failureStates.clear();
|
|
236
316
|
this.suppressed.clear();
|
|
317
|
+
this.readFailures = 0;
|
|
237
318
|
log('StrandWatcher stopped');
|
|
238
319
|
}
|
|
239
320
|
/**
|
|
240
|
-
* Forget a strand whose launch failed outside this watcher, so a later
|
|
241
|
-
* re-offers it — gated by the same backoff a watcher-driven failure gets
|
|
321
|
+
* Forget a strand whose launch failed outside this watcher, so a later run
|
|
322
|
+
* re-offers it — gated by the same backoff a watcher-driven failure gets, and woken
|
|
323
|
+
* when that backoff expires.
|
|
242
324
|
*
|
|
243
325
|
* A failed launch normally leaves nothing running (StrandInstanceManager drops the
|
|
244
326
|
* record), which is what makes re-offering it correct. One case does leave something
|
|
@@ -252,6 +334,25 @@ export class StrandWatcher {
|
|
|
252
334
|
this.knownStrands.delete(strandId);
|
|
253
335
|
this.provisional.delete(strandId);
|
|
254
336
|
this.recordFailure(strandId);
|
|
337
|
+
if (this.running) {
|
|
338
|
+
this.scheduleLaunchRetry(this.now());
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Re-read soon: something the queryable merges in beside the watched tables changed
|
|
343
|
+
* (`CadreNode`'s machine-local joined-strand records). A no-op once stopped.
|
|
344
|
+
*/
|
|
345
|
+
refresh() {
|
|
346
|
+
this.watch?.trigger();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* The host just learned `strandId`'s sAppId: if the strand was admitted provisionally
|
|
350
|
+
* under an `sAppId` filter, run so the admission resolves now rather than on the backstop.
|
|
351
|
+
*/
|
|
352
|
+
sAppIdLearned(strandId) {
|
|
353
|
+
if (this.provisional.has(strandId)) {
|
|
354
|
+
this.watch?.trigger();
|
|
355
|
+
}
|
|
255
356
|
}
|
|
256
357
|
/**
|
|
257
358
|
* Never offer this strand again this session. Two callers, both meaning "the retry
|
|
@@ -292,10 +393,13 @@ export class StrandWatcher {
|
|
|
292
393
|
return new Map(this.knownStrands);
|
|
293
394
|
}
|
|
294
395
|
/**
|
|
295
|
-
*
|
|
396
|
+
* Run once now, outside the watch's schedule (tests, and callers that just wrote a row
|
|
397
|
+
* and want this node to converge before returning). May overlap a scheduled run:
|
|
398
|
+
* `knownStrands` is updated before each callback's await, so neither launches nor
|
|
399
|
+
* removes a strand twice.
|
|
296
400
|
*/
|
|
297
401
|
async forcePoll() {
|
|
298
|
-
await this.
|
|
402
|
+
await this.runOnce();
|
|
299
403
|
}
|
|
300
404
|
}
|
|
301
405
|
//# sourceMappingURL=strand-watcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Database } from '@quereus/quereus';
|
|
2
|
+
import type { TimeoutScheduler } from './timeout-scheduler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default coalescing window. One local commit fires the watch twice (Quereus's own
|
|
5
|
+
* post-commit dispatch, then the plugin's storage event); this folds both into one run.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TABLE_CHANGE_COALESCE_MS = 50;
|
|
8
|
+
export interface TableChangeWatchOptions {
|
|
9
|
+
/** Short label for log lines (e.g. 'strand-watcher'). */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Coalescing window after the first change; default {@link TABLE_CHANGE_COALESCE_MS}. */
|
|
12
|
+
coalesceMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Run anyway this long after the last run settled, change or not. Omit for none.
|
|
15
|
+
* The path for a change that arrives without a storage event (see the module doc).
|
|
16
|
+
*/
|
|
17
|
+
backstopMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Called inline, synchronously, from the watch handler — inside the committing call,
|
|
20
|
+
* possibly with the control write lock held — once per changed table per event, with the
|
|
21
|
+
* name as the consumer passed it. Only for setting a flag (cache invalidation). Must not
|
|
22
|
+
* read, write or await.
|
|
23
|
+
*/
|
|
24
|
+
onInvalidate?: (qualifiedTable: string) => void;
|
|
25
|
+
/** Timer seam; default real, unref'd timers. */
|
|
26
|
+
scheduler?: TimeoutScheduler;
|
|
27
|
+
}
|
|
28
|
+
export interface TableChangeWatch {
|
|
29
|
+
/** Schedule a run after `delayMs` (default 0) through the same single-flight rule; an earlier pending run wins. */
|
|
30
|
+
trigger(delayMs?: number): void;
|
|
31
|
+
/** Stop: unsubscribe, clear timers; no run starts afterwards. Idempotent. Does not wait for a run in flight. */
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe `onChange` to changes in `tables` (qualified `'Schema.Table'` names, e.g.
|
|
36
|
+
* `'CadreControl.Strand'`). Throws synchronously on a name that is not qualified, or a table
|
|
37
|
+
* that does not exist.
|
|
38
|
+
*
|
|
39
|
+
* NOTE: the plugin arms its storage bridge for a table on that table's first full
|
|
40
|
+
* initialization on this `Database`, so a table not yet touched hears local commits only,
|
|
41
|
+
* not replicated ones. Consumers' start runs touch their tables; the seam does not, because
|
|
42
|
+
* touching a never-written collection consults the network.
|
|
43
|
+
*
|
|
44
|
+
* NOTE: Quereus silently disposes a subscription whose table is dropped or altered. Control
|
|
45
|
+
* and strand schemas are fixed for a database's life (an app-schema attach adds `App` tables
|
|
46
|
+
* and alters no `Strand` one), so nothing re-subscribes; revisit if that stops holding.
|
|
47
|
+
*/
|
|
48
|
+
export declare function watchTables(db: Database, tables: readonly string[], onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
|
|
49
|
+
/**
|
|
50
|
+
* {@link watchTables}' schedule — single-flight runs, `trigger`, the backstop — with no table
|
|
51
|
+
* subscription, for a consumer with no database to watch (tests, fakes).
|
|
52
|
+
*/
|
|
53
|
+
export declare function scheduleRuns(onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
|
|
54
|
+
/**
|
|
55
|
+
* The subscriptions one database owner has handed out, so its `close()` can dispose those
|
|
56
|
+
* still live — synchronously, ahead of its own awaits, so nothing a closing commit does
|
|
57
|
+
* schedules a run. (`Database.close()` disposes them too, but only after its own awaits.)
|
|
58
|
+
*/
|
|
59
|
+
export declare class TableWatchRegistry {
|
|
60
|
+
private readonly live;
|
|
61
|
+
watch(db: Database, tables: readonly string[], onChange: () => Promise<void>, options: TableChangeWatchOptions): TableChangeWatch;
|
|
62
|
+
disposeAll(): void;
|
|
63
|
+
}
|