@nimbus-sh/fabric 0.7.1 → 0.9.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 +26 -11
- package/dist/bindings.d.ts +1 -0
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +2 -0
- package/dist/budgets.d.ts +50 -29
- package/dist/budgets.d.ts.map +1 -1
- package/dist/budgets.js +88 -40
- package/dist/connections.d.ts +1 -1
- package/dist/connections.js +1 -1
- package/dist/do-calls.d.ts +91 -9
- package/dist/do-calls.d.ts.map +1 -1
- package/dist/do-calls.js +161 -22
- package/dist/facet-pool.d.ts +3 -0
- package/dist/facet-pool.d.ts.map +1 -1
- package/dist/facet-pool.js +3 -0
- package/dist/fanout.d.ts +40 -32
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +50 -53
- package/dist/fenced-work.d.ts +3 -3
- package/dist/host-wasm.d.ts +29 -0
- package/dist/host-wasm.d.ts.map +1 -0
- package/dist/host-wasm.js +31 -0
- package/dist/image-store.d.ts +1 -1
- package/dist/image-store.d.ts.map +1 -1
- package/dist/image-store.js +34 -2
- package/dist/inner-do-registry.d.ts +9 -0
- package/dist/inner-do-registry.d.ts.map +1 -1
- package/dist/inner-do-registry.js +35 -0
- package/dist/isolate-pool.d.ts +32 -24
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +78 -54
- package/dist/process-fabric.d.ts +42 -23
- package/dist/process-fabric.d.ts.map +1 -1
- package/dist/process-fabric.js +62 -6
- package/dist/process-host.d.ts +3 -1
- package/dist/process-host.d.ts.map +1 -1
- package/dist/process-host.js +13 -14
- package/dist/supervisor-props.d.ts +47 -0
- package/dist/supervisor-props.d.ts.map +1 -0
- package/dist/supervisor-props.js +37 -0
- package/dist/timers.d.ts +12 -0
- package/dist/timers.d.ts.map +1 -1
- package/dist/timers.js +44 -9
- package/dist/vendor/types.d.ts +11 -5
- package/dist/vendor/types.d.ts.map +1 -1
- package/dist/workerd-facet-host.d.ts +4 -17
- package/dist/workerd-facet-host.d.ts.map +1 -1
- package/dist/workerd-facet-host.js +120 -54
- package/package.json +6 -6
- package/src/bindings.ts +2 -0
- package/src/budgets.ts +96 -49
- package/src/connections.ts +1 -1
- package/src/do-calls.ts +216 -25
- package/src/facet-pool.ts +5 -0
- package/src/fanout.ts +64 -56
- package/src/fenced-work.ts +3 -3
- package/src/host-wasm.ts +41 -0
- package/src/image-store.ts +30 -3
- package/src/inner-do-registry.ts +31 -0
- package/src/isolate-pool.ts +108 -74
- package/src/process-fabric.ts +88 -30
- package/src/process-host.ts +16 -14
- package/src/supervisor-props.ts +56 -0
- package/src/timers.ts +45 -9
- package/src/vendor/types.ts +11 -5
- package/src/workerd-facet-host.ts +123 -58
package/dist/do-calls.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* the one property that decides whether a retry is safe.
|
|
4
4
|
*
|
|
5
5
|
* Both consumers asked for this. Proteus hand-wrote the retry
|
|
6
|
-
* (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
6
|
+
* (originally `cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
|
|
7
7
|
* "An operation that appends, sends, charges or mints is never wrapped: a
|
|
8
8
|
* dropped call there may already have run, so a retry is a correctness bug
|
|
9
9
|
* wearing resilience as a costume." agent-core has no retry machinery at all
|
|
10
10
|
* and its backlog calls the gap "the most production-proven gap in the
|
|
11
11
|
* corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
|
|
12
|
+
* A mutation earns a retry only by carrying an identity its callee applies
|
|
13
|
+
* at most once; it is then `idempotent` by construction (see `mutating`).
|
|
12
14
|
*
|
|
13
15
|
* What the platform contract requires, and this keeps:
|
|
14
16
|
* - a FRESH stub per attempt. Cloudflare documents that many exceptions
|
|
@@ -20,6 +22,11 @@
|
|
|
20
22
|
* object is what overloaded it.
|
|
21
23
|
* - attempts and backoff are the consumer-proven bounds: 3 attempts total,
|
|
22
24
|
* full-jitter delays in [0, 2**attempt * 60ms).
|
|
25
|
+
* - an `idempotent` call may also be HEDGED (`hedgeAfterMs`): an attempt
|
|
26
|
+
* that has not answered by then is joined by the same call on a fresh
|
|
27
|
+
* stub, both left running, the first success taken. Hedges count
|
|
28
|
+
* against the attempts, and a callee that joins a repeat to the call it
|
|
29
|
+
* is already serving makes one that did arrive cost nothing.
|
|
23
30
|
*
|
|
24
31
|
* The resolver MINTS a stub per call and the verb disposes each one it
|
|
25
32
|
* minted — that ownership is what makes the fresh-stub retry real.
|
|
@@ -34,6 +41,7 @@
|
|
|
34
41
|
*/
|
|
35
42
|
import { classifyDoCall, isRetryableDoCall } from '@nimbus-sh/platform/oom-classify.js';
|
|
36
43
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
44
|
+
import { untraced } from '@nimbus-sh/platform/tracing.js';
|
|
37
45
|
/** Total attempts. Two retries is what a dropped connection or a deploy
|
|
38
46
|
* bounce needs; beyond that the object is not coming back inside this
|
|
39
47
|
* request (the consumer's measured bound). */
|
|
@@ -64,38 +72,169 @@ export class DoCallError extends Error {
|
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
74
|
* Call another Durable Object with an operation that is safe to repeat: a
|
|
67
|
-
* read,
|
|
68
|
-
*
|
|
69
|
-
*
|
|
75
|
+
* read, a converge-to-a-value write, or a mutation carrying an identity its
|
|
76
|
+
* callee applies at most once (see {@link mutating}). Transient failures
|
|
77
|
+
* retry on a fresh stub with full-jitter backoff; overloaded and permanent
|
|
78
|
+
* failures surface unchanged, as does the last error once no attempt may be
|
|
79
|
+
* repeated — attempts spent, or the policy's retry window closed. With
|
|
80
|
+
* `hedgeAfterMs`, an attempt still unanswered by then is joined by another
|
|
81
|
+
* on a fresh stub, and the first answer is taken: a success, or the
|
|
82
|
+
* callee's own error. A transient or overloaded failure then ends the call
|
|
83
|
+
* only once no attempt is left in flight.
|
|
84
|
+
*
|
|
85
|
+
* A failure of the resolver or of `onRetry` is the caller's own, and fails
|
|
86
|
+
* the call with it at once.
|
|
70
87
|
*/
|
|
71
|
-
export
|
|
88
|
+
export function idempotent(operation, stub, call, policy = {}) {
|
|
72
89
|
const maxAttempts = policy.maxAttempts ?? MAX_ATTEMPTS;
|
|
73
90
|
const baseDelayMs = policy.baseDelayMs ?? BASE_DELAY_MS;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
const { hedgeAfterMs, retryWindowMs, span = untraced } = policy;
|
|
92
|
+
const startedAt = Date.now();
|
|
93
|
+
// The executor form: fabric's library target predates Promise.withResolvers.
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const hedges = new Set();
|
|
96
|
+
let started = 0;
|
|
97
|
+
// Attempts in flight, or backing off before their retry: while one is,
|
|
98
|
+
// a failure is not the call's answer.
|
|
99
|
+
let live = 0;
|
|
100
|
+
// An attempt was shed as overloaded: nothing is repeated after it.
|
|
101
|
+
let refused = false;
|
|
102
|
+
let settled = false;
|
|
103
|
+
// Attempts a hedge started rather than a retry or the first send.
|
|
104
|
+
let hedged = 0;
|
|
105
|
+
const settle = (outcome, answeredBy, answer) => {
|
|
106
|
+
if (settled)
|
|
107
|
+
return;
|
|
108
|
+
settled = true;
|
|
109
|
+
for (const timer of hedges)
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
hedges.clear();
|
|
112
|
+
answer();
|
|
113
|
+
span.set({
|
|
114
|
+
'do_call.attempts': started,
|
|
115
|
+
'do_call.hedges': hedged,
|
|
116
|
+
'do_call.answered_by': answeredBy,
|
|
117
|
+
'do_call.outcome': outcome,
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
/** May another attempt start at `at`? */
|
|
121
|
+
const canRepeat = (at) => !settled && !refused && started < maxAttempts
|
|
122
|
+
&& (retryWindowMs === undefined || at - startedAt <= retryWindowMs);
|
|
123
|
+
/** `error` ended an attempt that will not be repeated: the call's answer, once nothing else is live. */
|
|
124
|
+
const exhausted = (error) => {
|
|
125
|
+
if (live === 0)
|
|
126
|
+
settle(refused ? 'overloaded' : 'exhausted', undefined, () => reject(error));
|
|
127
|
+
};
|
|
128
|
+
/** A failed attempt, numbered: repeat it after its backoff, or let it stand. */
|
|
129
|
+
const failed = async (number, error) => {
|
|
130
|
+
if (settled)
|
|
131
|
+
return;
|
|
84
132
|
const classification = classifyDoCall(error);
|
|
85
|
-
if (!isRetryableDoCall(classification)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
if (!isRetryableDoCall(classification) && classification !== 'overloaded') {
|
|
134
|
+
// The call ran and its answer is this error — ENOENT is a read's answer as much as bytes are.
|
|
135
|
+
settle('callee_error', number, () => reject(error));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// A lost attempt: the call's span says which, and why.
|
|
139
|
+
span.exception(error, classification, `attempt ${number} of ${maxAttempts}: `);
|
|
140
|
+
if (classification === 'overloaded') {
|
|
141
|
+
// A shed call is no answer: nothing more is sent, and an attempt
|
|
142
|
+
// still in flight may yet answer.
|
|
143
|
+
refused = true;
|
|
144
|
+
exhausted(error);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const delayMs = Math.floor(Math.random() * 2 ** number * baseDelayMs);
|
|
148
|
+
if (!canRepeat(Date.now() + delayMs)) {
|
|
149
|
+
exhausted(error);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
live++;
|
|
153
|
+
await new Promise((wake) => {
|
|
154
|
+
setTimeout(wake, delayMs);
|
|
90
155
|
});
|
|
91
|
-
|
|
92
|
-
|
|
156
|
+
live--;
|
|
157
|
+
// A hedge may have taken the last attempt, or answered, meanwhile.
|
|
158
|
+
if (!canRepeat(Date.now())) {
|
|
159
|
+
exhausted(error);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
policy.onRetry?.({ operation, classification, attempt: number, maxAttempts, error });
|
|
163
|
+
attempt();
|
|
164
|
+
};
|
|
165
|
+
const run = async () => {
|
|
166
|
+
const number = ++started;
|
|
167
|
+
live++;
|
|
168
|
+
const minted = await stub();
|
|
169
|
+
if (settled) {
|
|
170
|
+
live--;
|
|
171
|
+
disposeRpcResource(minted);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const hedge = hedgeAfterMs === undefined ? undefined : setTimeout(() => {
|
|
175
|
+
if (hedge !== undefined)
|
|
176
|
+
hedges.delete(hedge);
|
|
177
|
+
if (canRepeat(Date.now())) {
|
|
178
|
+
hedged++;
|
|
179
|
+
attempt();
|
|
180
|
+
}
|
|
181
|
+
}, hedgeAfterMs);
|
|
182
|
+
if (hedge !== undefined)
|
|
183
|
+
hedges.add(hedge);
|
|
184
|
+
/** This attempt has its answer: it hedges no more, and its stub goes. */
|
|
185
|
+
const answered = () => {
|
|
186
|
+
if (hedge !== undefined) {
|
|
187
|
+
clearTimeout(hedge);
|
|
188
|
+
hedges.delete(hedge);
|
|
189
|
+
}
|
|
190
|
+
live--;
|
|
191
|
+
// A stub that threw may be permanently broken; none is ever reused.
|
|
192
|
+
disposeRpcResource(minted);
|
|
193
|
+
};
|
|
194
|
+
let result;
|
|
195
|
+
try {
|
|
196
|
+
result = await call(minted);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
answered();
|
|
200
|
+
await failed(number, error);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
answered();
|
|
204
|
+
if (settled) {
|
|
205
|
+
// Another attempt answered first: this answer is dropped, so nothing of it is kept.
|
|
206
|
+
disposeRpcResource(result);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
settle('answered', number, () => resolve(result));
|
|
210
|
+
};
|
|
211
|
+
/** Start an attempt. Whatever it throws outside the call itself fails the call. */
|
|
212
|
+
const attempt = () => {
|
|
213
|
+
run().catch((error) => settle('caller_error', undefined, () => reject(error)));
|
|
214
|
+
};
|
|
215
|
+
attempt();
|
|
216
|
+
});
|
|
93
217
|
}
|
|
94
218
|
/**
|
|
95
219
|
* Call another Durable Object with an operation that appends, sends, charges
|
|
96
220
|
* or mints. NEVER retried — a dropped call may already have run. Failure
|
|
97
221
|
* surfaces as a {@link DoCallError} carrying the classification, so the
|
|
98
222
|
* caller can tell a refusal from an indeterminate drop.
|
|
223
|
+
*
|
|
224
|
+
* The rule is about the call as sent, not the operation's kind. A mutation
|
|
225
|
+
* the callee applies at most once per identity the call carries is
|
|
226
|
+
* repeatable by construction: a repeat of one that already ran is answered
|
|
227
|
+
* from the callee's record and applies nothing. Nimbus has two:
|
|
228
|
+
* - delivered filesystem mutations (@nimbus-sh/core supervisor-delivery):
|
|
229
|
+
* a delivery id plus the callee INSTANCE's incarnation. The record lives
|
|
230
|
+
* in that instance's memory, and any other instance — or a callee that
|
|
231
|
+
* predates delivery — refuses the call permanently rather than apply it
|
|
232
|
+
* without one;
|
|
233
|
+
* - appends: writer, module incarnation and operation sequence, recorded
|
|
234
|
+
* durably until acknowledged.
|
|
235
|
+
* Such a call goes through {@link idempotent}, re-sending the same identity
|
|
236
|
+
* on every attempt, with a `retryWindowMs` inside the callee's retention of
|
|
237
|
+
* that record. Without such an identity, a mutation stays here.
|
|
99
238
|
*/
|
|
100
239
|
export async function mutating(operation, stub, call) {
|
|
101
240
|
const minted = await stub();
|
package/dist/facet-pool.d.ts
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* whichever acts second aborts or retires facets the other still tracks,
|
|
42
42
|
* and the facet-id ledger here counts only the names this pool minted.
|
|
43
43
|
*/
|
|
44
|
+
import type { SqlDatabase } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
44
45
|
/** `ctx.facets`, as the pool drives it — same surface the facet host uses. */
|
|
45
46
|
export interface FacetPoolContainer {
|
|
46
47
|
get(name: string, start: () => Promise<{
|
|
@@ -56,6 +57,8 @@ export interface FacetPoolContext {
|
|
|
56
57
|
storage: {
|
|
57
58
|
get(key: string): Promise<unknown> | unknown;
|
|
58
59
|
put(key: string, value: unknown): Promise<void>;
|
|
60
|
+
/** The session's SQL, where the storage ledger (N18) records facet databases. */
|
|
61
|
+
sql?: SqlDatabase;
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
/**
|
package/dist/facet-pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;
|
|
1
|
+
{"version":3,"file":"facet-pool.d.ts","sourceRoot":"","sources":["../src/facet-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAQ3E,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC;IACrE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;wCACwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE;QACP,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,iFAAiF;QACjF,GAAG,CAAC,EAAE,WAAW,CAAC;KACnB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,gEAAgE;IAChE,MAAM,IAAI,IAAI,CAAC;IACf,sEAAsE;IACtE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAKD,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAE1D;AAED,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,gBAAgB;IAElD;;;;OAIG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GACvC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAkD1B"}
|
package/dist/facet-pool.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* whichever acts second aborts or retires facets the other still tracks,
|
|
42
42
|
* and the facet-id ledger here counts only the names this pool minted.
|
|
43
43
|
*/
|
|
44
|
+
import { forgetFacetStorage } from '@nimbus-sh/core/runtime/storage-ledger.js';
|
|
44
45
|
import { FACET_ID_LIFETIME_BUDGET, facetNameCount, recordFacetNameMinted, withFacetBudgetNamed, } from './budgets.js';
|
|
45
46
|
/** Names this ctx's pool has already charged to the lifetime ledger. */
|
|
46
47
|
const chargedNames = new WeakMap();
|
|
@@ -93,6 +94,8 @@ export class FacetPool {
|
|
|
93
94
|
return;
|
|
94
95
|
try {
|
|
95
96
|
facets.delete(name);
|
|
97
|
+
if (this.ctx.storage.sql)
|
|
98
|
+
forgetFacetStorage(this.ctx.storage.sql, name);
|
|
96
99
|
}
|
|
97
100
|
catch (e) {
|
|
98
101
|
throw new Error(`fabric: facet '${name}' was evicted but its storage was not reclaimed — `
|
package/dist/fanout.d.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Two-tier fan-out primitive for work that must execute in Worker Loader
|
|
3
|
-
*
|
|
3
|
+
* isolates within the Durable Object's Dynamic Worker budget.
|
|
4
4
|
*
|
|
5
|
-
* A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* A Durable Object may have `DO_DYNAMIC_WORKER_LIMIT` distinct Dynamic
|
|
6
|
+
* Workers with in-flight requests, shared across every concurrent request to
|
|
7
|
+
* it (budgets.ts). A batch the coordinator's remaining headroom can hold —
|
|
8
|
+
* the limit less the workers it already has in flight (resident processes,
|
|
9
|
+
* the esbuild facet, a git network op) and other fan-outs' claims — runs in
|
|
10
|
+
* the coordinator through IsolatePool, one Dynamic Worker per task. Only a
|
|
11
|
+
* batch wider than that headroom is sharded across sibling NimbusSession
|
|
12
|
+
* DOs, each of which spends its own budget.
|
|
10
13
|
*
|
|
11
|
-
* Routing is deterministic: each task has a stable key,
|
|
12
|
-
* to a sibling DO shard. There is no silent fallback to
|
|
13
|
-
* missing LOADER or NIMBUS_SESSION bindings fail loudly so
|
|
14
|
-
* runtime operations do not appear successful after partial
|
|
14
|
+
* Routing is deterministic for a given headroom: each task has a stable key,
|
|
15
|
+
* and the key maps to a sibling DO shard. There is no silent fallback to
|
|
16
|
+
* width-1 execution; missing LOADER or NIMBUS_SESSION bindings fail loudly so
|
|
17
|
+
* install and runtime operations do not appear successful after partial
|
|
18
|
+
* dispatch.
|
|
15
19
|
*/
|
|
16
20
|
import { type FacetTaskFn } from './isolate-pool.js';
|
|
17
21
|
import type { WorkerLoader } from './vendor/types.js';
|
|
@@ -22,14 +26,16 @@ export interface FanoutEnv {
|
|
|
22
26
|
LOADER?: WorkerLoader;
|
|
23
27
|
NIMBUS_SESSION?: unknown;
|
|
24
28
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/** Where one `submitMany` ran. */
|
|
30
|
+
export type FanoutTopology = 'in-do' | 'peer-do';
|
|
31
|
+
/** The routing decision for one `submitMany`, as its caller may log it. */
|
|
32
|
+
export interface FanoutRoute {
|
|
33
|
+
topology: FanoutTopology;
|
|
34
|
+
/** Tasks in the batch — the Dynamic Workers an in-DO run spends. */
|
|
35
|
+
tasks: number;
|
|
36
|
+
/** The coordinator's Dynamic Worker headroom when the batch was routed. */
|
|
37
|
+
headroom: number;
|
|
38
|
+
}
|
|
33
39
|
/**
|
|
34
40
|
* Hard cap on concurrent peer DOs per single submitMany call. Throughput stays
|
|
35
41
|
* flat through this width while keeping per-request scheduler pressure bounded.
|
|
@@ -134,12 +140,14 @@ export interface FanoutOptions {
|
|
|
134
140
|
* Not called on the in-DO path, which has no phases.
|
|
135
141
|
*/
|
|
136
142
|
onDispatchPhase?: (width: number, elapsedMs: number) => void;
|
|
143
|
+
/** Called once per non-empty submitMany with the route it took. */
|
|
144
|
+
onRoute?: (route: FanoutRoute) => void;
|
|
137
145
|
/**
|
|
138
146
|
* Cap on peer DOs this pool will spread one submitMany across. Defaults to
|
|
139
147
|
* MAX_PEER_FANOUT. Tasks beyond the cap bucket into the peers that exist and
|
|
140
148
|
* run through their in-peer pool, so lowering it trades peers for barriers
|
|
141
|
-
* without lowering total concurrency: each peer runs its bucket
|
|
142
|
-
*
|
|
149
|
+
* without lowering total concurrency: each peer runs its bucket as wide as
|
|
150
|
+
* its own Dynamic Worker headroom allows.
|
|
143
151
|
*
|
|
144
152
|
* A caller sets this when its per-task work is small enough that a peer per
|
|
145
153
|
* task buys nothing but round-trips — one task per peer costs ⌈tasks/
|
|
@@ -149,7 +157,7 @@ export interface FanoutOptions {
|
|
|
149
157
|
}
|
|
150
158
|
/**
|
|
151
159
|
* Two-tier fan-out pool. Constructed by the supervisor DO; routes
|
|
152
|
-
* each `submitMany` call
|
|
160
|
+
* each `submitMany` call on the coordinator's live Dynamic Worker headroom.
|
|
153
161
|
*
|
|
154
162
|
* Lifetime: cheap to construct (no async init). Multiple submitMany
|
|
155
163
|
* calls share NO state — each is dispatched fresh. The class
|
|
@@ -167,16 +175,16 @@ export declare class Fanout {
|
|
|
167
175
|
* Dispatch `tasks` across the appropriate topology and return
|
|
168
176
|
* results in input order.
|
|
169
177
|
*
|
|
170
|
-
* Routing:
|
|
171
|
-
* tasks.length
|
|
172
|
-
*
|
|
178
|
+
* Routing, against the coordinator's Dynamic Worker headroom at call time:
|
|
179
|
+
* tasks.length <= headroom -> coordinator-local IsolatePool, one Dynamic
|
|
180
|
+
* Worker per task, the width claimed on the
|
|
181
|
+
* ledger until the batch settles
|
|
182
|
+
* tasks.length > headroom -> sibling NimbusSession DOs
|
|
173
183
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* single submitMany call returns when ALL tasks complete (or any
|
|
179
|
-
* throws).
|
|
184
|
+
* Peer shards: tasks hash onto min(tasks, maxPeers ?? MAX_PEER_FANOUT)
|
|
185
|
+
* peers and each peer runs its bucket through its own IsolatePool, as
|
|
186
|
+
* wide as that peer's headroom allows. A single submitMany call returns
|
|
187
|
+
* when ALL tasks complete (or any throws).
|
|
180
188
|
*
|
|
181
189
|
* `fn` is the user function executed per task. It runs INSIDE a
|
|
182
190
|
* Worker Loader isolate (in the in-DO path) or inside a peer DO's
|
|
@@ -185,8 +193,8 @@ export declare class Fanout {
|
|
|
185
193
|
* the vendored serializeFunction (same as IsolatePool#prepare).
|
|
186
194
|
*/
|
|
187
195
|
submitMany<A, R>(tasks: FanoutTask<A>[], fn: FacetTaskFn<A, R>): Promise<R[]>;
|
|
188
|
-
/**
|
|
189
|
-
topologyFor(taskCount: number):
|
|
196
|
+
/** The topology a task count would take against the headroom right now. */
|
|
197
|
+
topologyFor(taskCount: number): FanoutTopology | 'empty';
|
|
190
198
|
/**
|
|
191
199
|
* Compute the deterministic peer-DO id for a task key and peer count.
|
|
192
200
|
*
|
package/dist/fanout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"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"}
|
package/dist/fanout.js
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Two-tier fan-out primitive for work that must execute in Worker Loader
|
|
3
|
-
*
|
|
3
|
+
* isolates within the Durable Object's Dynamic Worker budget.
|
|
4
4
|
*
|
|
5
|
-
* A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* A Durable Object may have `DO_DYNAMIC_WORKER_LIMIT` distinct Dynamic
|
|
6
|
+
* Workers with in-flight requests, shared across every concurrent request to
|
|
7
|
+
* it (budgets.ts). A batch the coordinator's remaining headroom can hold —
|
|
8
|
+
* the limit less the workers it already has in flight (resident processes,
|
|
9
|
+
* the esbuild facet, a git network op) and other fan-outs' claims — runs in
|
|
10
|
+
* the coordinator through IsolatePool, one Dynamic Worker per task. Only a
|
|
11
|
+
* batch wider than that headroom is sharded across sibling NimbusSession
|
|
12
|
+
* DOs, each of which spends its own budget.
|
|
10
13
|
*
|
|
11
|
-
* Routing is deterministic: each task has a stable key,
|
|
12
|
-
* to a sibling DO shard. There is no silent fallback to
|
|
13
|
-
* missing LOADER or NIMBUS_SESSION bindings fail loudly so
|
|
14
|
-
* runtime operations do not appear successful after partial
|
|
14
|
+
* Routing is deterministic for a given headroom: each task has a stable key,
|
|
15
|
+
* and the key maps to a sibling DO shard. There is no silent fallback to
|
|
16
|
+
* width-1 execution; missing LOADER or NIMBUS_SESSION bindings fail loudly so
|
|
17
|
+
* install and runtime operations do not appear successful after partial
|
|
18
|
+
* dispatch.
|
|
15
19
|
*/
|
|
16
20
|
import { serializeFunction } from './vendor/serialize.js';
|
|
17
21
|
import { BindingError } from './vendor/errors.js';
|
|
18
22
|
import { IsolatePool } from './isolate-pool.js';
|
|
23
|
+
import { claimDynamicWorkers, dynamicWorkerHeadroom } from './budgets.js';
|
|
19
24
|
import { hostRoute } from './composition.js';
|
|
20
25
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
21
|
-
import {
|
|
26
|
+
import { classifyDoCall, describeError, isRetryableDoCall } from '@nimbus-sh/platform/oom-classify.js';
|
|
22
27
|
import { hostNamespaceBinding, hostOpDispatch, } from './host-dispatch.js';
|
|
23
|
-
/**
|
|
24
|
-
* Threshold at which routing switches from coordinator-local loaders to
|
|
25
|
-
* sibling Durable Objects.
|
|
26
|
-
*
|
|
27
|
-
* Set to **5** so the in-DO path stays below the V8 4-loaders-per-method
|
|
28
|
-
* cap by construction. width < 5 stays local; width >= 5 uses sibling DOs.
|
|
29
|
-
*/
|
|
30
|
-
export const IN_DO_THRESHOLD = 5;
|
|
31
28
|
/**
|
|
32
29
|
* Hard cap on concurrent peer DOs per single submitMany call. Throughput stays
|
|
33
30
|
* flat through this width while keeping per-request scheduler pressure bounded.
|
|
@@ -78,7 +75,7 @@ function isPeerResult(value) {
|
|
|
78
75
|
}
|
|
79
76
|
/**
|
|
80
77
|
* Two-tier fan-out pool. Constructed by the supervisor DO; routes
|
|
81
|
-
* each `submitMany` call
|
|
78
|
+
* each `submitMany` call on the coordinator's live Dynamic Worker headroom.
|
|
82
79
|
*
|
|
83
80
|
* Lifetime: cheap to construct (no async init). Multiple submitMany
|
|
84
81
|
* calls share NO state — each is dispatched fresh. The class
|
|
@@ -112,16 +109,16 @@ export class Fanout {
|
|
|
112
109
|
* Dispatch `tasks` across the appropriate topology and return
|
|
113
110
|
* results in input order.
|
|
114
111
|
*
|
|
115
|
-
* Routing:
|
|
116
|
-
* tasks.length
|
|
117
|
-
*
|
|
112
|
+
* Routing, against the coordinator's Dynamic Worker headroom at call time:
|
|
113
|
+
* tasks.length <= headroom -> coordinator-local IsolatePool, one Dynamic
|
|
114
|
+
* Worker per task, the width claimed on the
|
|
115
|
+
* ledger until the batch settles
|
|
116
|
+
* tasks.length > headroom -> sibling NimbusSession DOs
|
|
118
117
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* single submitMany call returns when ALL tasks complete (or any
|
|
124
|
-
* throws).
|
|
118
|
+
* Peer shards: tasks hash onto min(tasks, maxPeers ?? MAX_PEER_FANOUT)
|
|
119
|
+
* peers and each peer runs its bucket through its own IsolatePool, as
|
|
120
|
+
* wide as that peer's headroom allows. A single submitMany call returns
|
|
121
|
+
* when ALL tasks complete (or any throws).
|
|
125
122
|
*
|
|
126
123
|
* `fn` is the user function executed per task. It runs INSIDE a
|
|
127
124
|
* Worker Loader isolate (in the in-DO path) or inside a peer DO's
|
|
@@ -132,16 +129,23 @@ export class Fanout {
|
|
|
132
129
|
async submitMany(tasks, fn) {
|
|
133
130
|
if (tasks.length === 0)
|
|
134
131
|
return [];
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
const headroom = dynamicWorkerHeadroom(this.ctx);
|
|
133
|
+
const claim = claimDynamicWorkers(this.ctx, tasks.length);
|
|
134
|
+
this.opts.onRoute?.({ topology: claim ? 'in-do' : 'peer-do', tasks: tasks.length, headroom });
|
|
135
|
+
if (!claim)
|
|
136
|
+
return this._dispatchPeerDo(tasks, fn);
|
|
137
|
+
try {
|
|
138
|
+
return await this._dispatchInDo(tasks, fn);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
claim.release();
|
|
137
142
|
}
|
|
138
|
-
return this._dispatchPeerDo(tasks, fn);
|
|
139
143
|
}
|
|
140
|
-
/**
|
|
144
|
+
/** The topology a task count would take against the headroom right now. */
|
|
141
145
|
topologyFor(taskCount) {
|
|
142
146
|
if (taskCount === 0)
|
|
143
147
|
return 'empty';
|
|
144
|
-
return taskCount
|
|
148
|
+
return taskCount <= dynamicWorkerHeadroom(this.ctx) ? 'in-do' : 'peer-do';
|
|
145
149
|
}
|
|
146
150
|
/**
|
|
147
151
|
* Compute the deterministic peer-DO id for a task key and peer count.
|
|
@@ -156,13 +160,10 @@ export class Fanout {
|
|
|
156
160
|
}
|
|
157
161
|
// ── Private: in-DO dispatch (in-DO fanout) ──────────────────────────────
|
|
158
162
|
async _dispatchInDo(tasks, fn) {
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// here, so the cap won't bite). Each task = one pool.submit;
|
|
162
|
-
// pool.map runs them with stable-slot reuse.
|
|
163
|
-
const concurrency = Math.min(tasks.length, IN_DO_THRESHOLD - 1);
|
|
163
|
+
// One slot — one Dynamic Worker — per task; submitMany has claimed
|
|
164
|
+
// that width on the ledger.
|
|
164
165
|
const pool = new IsolatePool(this.env, this.ctx, {
|
|
165
|
-
concurrency,
|
|
166
|
+
concurrency: tasks.length,
|
|
166
167
|
timeoutMs: this.opts.timeoutMs,
|
|
167
168
|
tag: this.opts.tag,
|
|
168
169
|
preamble: this.opts.preamble,
|
|
@@ -172,9 +173,6 @@ export class Fanout {
|
|
|
172
173
|
supervisorPid: this.opts.supervisorPid,
|
|
173
174
|
});
|
|
174
175
|
try {
|
|
175
|
-
// pool.map runs the function over `items` with concurrency-bounded
|
|
176
|
-
// slot reuse. Each slot is one warm loader isolate; we get exactly
|
|
177
|
-
// `concurrency` loader isolates total — well under the 4-cap.
|
|
178
176
|
const items = tasks.map((t) => t.args);
|
|
179
177
|
const results = await pool.map(fn, items);
|
|
180
178
|
// pool.map returns Array<R | null> (null on per-item failure with
|
|
@@ -200,10 +198,9 @@ export class Fanout {
|
|
|
200
198
|
// loader isolates (keyed on fnHash) reuse across calls with
|
|
201
199
|
// identical fns.
|
|
202
200
|
const fnSource = serializeFunction(fn);
|
|
203
|
-
// Cap peer count at MAX_PEER_FANOUT. Tasks beyond
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
// (concurrency capped at 4 there).
|
|
201
|
+
// Cap peer count at maxPeers (default MAX_PEER_FANOUT). Tasks beyond
|
|
202
|
+
// it bucket into existing shards; each peer runs its bucket through
|
|
203
|
+
// its own IsolatePool, as wide as its own headroom allows.
|
|
207
204
|
const peerCount = Math.min(tasks.length, this.opts.maxPeers ?? MAX_PEER_FANOUT);
|
|
208
205
|
// Group tasks by deterministic shard. Same key → same shard, so
|
|
209
206
|
// tests can predict which peer handles which task.
|
|
@@ -242,10 +239,9 @@ export class Fanout {
|
|
|
242
239
|
const peerStub = ns.get(id);
|
|
243
240
|
try {
|
|
244
241
|
const dispatch = hostOpDispatch(peerStub, `Fanout peer ${siblingName}`);
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
// makes peer-DO fanout work.
|
|
242
|
+
// A peer DO call is a Durable Object RPC, not a Dynamic Worker:
|
|
243
|
+
// it spends none of this coordinator's budget, and the peer
|
|
244
|
+
// spends its own.
|
|
249
245
|
const rpcResp = await dispatch({
|
|
250
246
|
op: 'fanoutExecute',
|
|
251
247
|
args: [
|
|
@@ -297,8 +293,9 @@ export class Fanout {
|
|
|
297
293
|
return;
|
|
298
294
|
}
|
|
299
295
|
catch (err) {
|
|
300
|
-
const
|
|
301
|
-
|
|
296
|
+
const cls = classifyDoCall(err);
|
|
297
|
+
const schedule = cls === 'overloaded' ? PEER_OVERLOAD_BACKOFF_MS
|
|
298
|
+
: isRetryableDoCall(cls) ? PEER_RETRY_BACKOFF_MS
|
|
302
299
|
: null;
|
|
303
300
|
if (schedule && attempt < PEER_TRANSIENT_RESET_RETRIES) {
|
|
304
301
|
const backoff = schedule[Math.min(attempt, schedule.length - 1)];
|
package/dist/fenced-work.d.ts
CHANGED
|
@@ -53,9 +53,9 @@ export declare const FENCED_WORK_MAX_ATTEMPT = 1;
|
|
|
53
53
|
* (staging, 2026-08-13): every observed reset struck seconds AFTER the launch
|
|
54
54
|
* settled — the platform kills the object while the resident runs, which is
|
|
55
55
|
* when a launch-scoped row had already been deleted and recovery had nothing
|
|
56
|
-
* to find. A resident's facet
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* to find. A resident's facet can outlive its session instance (a pending
|
|
57
|
+
* timer keeps it running), but nothing routes to it from the next one, and a
|
|
58
|
+
* spawn that takes its facet name ends it first.
|
|
59
59
|
*/
|
|
60
60
|
export interface FencedWorkRecord {
|
|
61
61
|
pid: number;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host-wasm.ts — what the fabric knows about a WebAssembly.Module a host
|
|
3
|
+
* hands a dynamic worker already compiled.
|
|
4
|
+
*
|
|
5
|
+
* workerd accepts a compiled module in a Worker Loader module map and lets
|
|
6
|
+
* the dynamic worker share its compiled code (src/workerd/api/
|
|
7
|
+
* worker-loader.c++, extractWasmModuleContent), so a host that bundles a
|
|
8
|
+
* fixed wasm (esbuild's, say) hands that module over instead of fetching a
|
|
9
|
+
* second copy of the bytes and having the guest compile them again. Two
|
|
10
|
+
* things the fabric needs about a member are unreadable from JS for a
|
|
11
|
+
* Module:
|
|
12
|
+
* - its size: the module's wire bytes still count toward the 64 MiB
|
|
13
|
+
* dynamic-worker code limit (worker-loader.c++ sums every member), and
|
|
14
|
+
* assertModuleMapWithinCodeLimit must count them;
|
|
15
|
+
* - its identity: a pool folds its wasm into the loader cache key, and a
|
|
16
|
+
* Module has no bytes to fingerprint.
|
|
17
|
+
* The host that owns the module states both once, here.
|
|
18
|
+
*/
|
|
19
|
+
export interface HostWasmIdentity {
|
|
20
|
+
/** A stable name for the module's content, e.g. `esbuild@0.24.2`. */
|
|
21
|
+
readonly id: string;
|
|
22
|
+
/** The module's wire bytes: what it costs the dynamic-worker code budget. */
|
|
23
|
+
readonly bytes: number;
|
|
24
|
+
}
|
|
25
|
+
/** Record what `module` is, and return it. */
|
|
26
|
+
export declare function describeHostWasm(module: WebAssembly.Module, identity: HostWasmIdentity): WebAssembly.Module;
|
|
27
|
+
/** What a host said `module` is, or undefined for a module nobody described. */
|
|
28
|
+
export declare function hostWasmIdentity(module: WebAssembly.Module): HostWasmIdentity | undefined;
|
|
29
|
+
//# sourceMappingURL=host-wasm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-wasm.d.ts","sourceRoot":"","sources":["../src/host-wasm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAID,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAM3G;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzF"}
|