@hew-lang/sandbox-vm 0.6.1 → 0.6.3
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 +48 -4
- package/cli/test-runner.mjs +4 -1
- package/dist/interpreter/interpreter.d.ts +1 -1
- package/dist/interpreter/interpreter.js +1 -1
- package/dist/interpreter/parity-runner.js +1 -1
- package/dist/interpreter/run-program.d.ts +3 -2
- package/dist/interpreter/run-program.js +5 -3
- package/dist/interpreter/schema-validator.d.ts +9 -4
- package/dist/interpreter/schema-validator.js +15 -49
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +6 -0
- package/dist/interpreter/unicode-data.d.ts +4 -0
- package/dist/interpreter/unicode-data.js +1673 -0
- package/dist/interpreter/unicode.d.ts +3 -0
- package/dist/interpreter/unicode.js +35 -0
- package/dist/interpreter/v1/actor-identity.d.ts +3 -0
- package/dist/interpreter/v1/actor-identity.js +7 -0
- package/dist/interpreter/v1/actor-waits.d.ts +6 -0
- package/dist/interpreter/v1/actor-waits.js +33 -0
- package/dist/interpreter/v1/browser-directory.d.ts +107 -0
- package/dist/interpreter/v1/browser-directory.js +288 -0
- package/dist/interpreter/v1/calendar.d.ts +4 -0
- package/dist/interpreter/v1/calendar.js +64 -0
- package/dist/interpreter/v1/codecs.js +8 -11
- package/dist/interpreter/v1/exec.d.ts +2 -2
- package/dist/interpreter/v1/exec.js +1761 -489
- package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
- package/dist/interpreter/v1/instructions.generated.js +106 -0
- package/dist/interpreter/v1/package-graph.d.ts +2 -0
- package/dist/interpreter/v1/package-graph.js +729 -0
- package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
- package/dist/interpreter/v1/package-validator.generated.js +75902 -0
- package/dist/interpreter/v1/package.d.ts +5 -645
- package/dist/interpreter/v1/package.js +0 -12
- package/dist/interpreter/v1/pipes.d.ts +1 -0
- package/dist/interpreter/v1/pipes.js +9 -6
- package/dist/interpreter/v1/scheduler.d.ts +8 -1
- package/dist/interpreter/v1/scheduler.js +50 -11
- package/dist/interpreter/v1/shims.d.ts +260 -7
- package/dist/interpreter/v1/shims.js +647 -175
- package/dist/interpreter/v1/structural.d.ts +2 -28
- package/dist/interpreter/v1/timestamp.d.ts +7 -0
- package/dist/interpreter/v1/timestamp.js +54 -0
- package/dist/interpreter/v1/validate.d.ts +8 -2
- package/dist/interpreter/v1/validate.js +284 -200
- package/dist/interpreter/v1/value-operations.d.ts +11 -3
- package/dist/interpreter/v1/value-operations.js +70 -18
- package/dist/interpreter/values.d.ts +30 -8
- package/dist/interpreter/values.js +31 -5
- package/dist/interpreter/worker.js +4 -3
- package/package.json +4 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BrowserDirectories, } from "./browser-directory.js";
|
|
1
2
|
/// The `hew.sandbox.bytecode.v1` executor.
|
|
2
3
|
///
|
|
3
4
|
/// The package is a projection of verified ownership SIR, so this executor
|
|
@@ -6,18 +7,22 @@
|
|
|
6
7
|
/// follows the `unwind` edge the package names, and takes the failure edge a
|
|
7
8
|
/// `checked.binary` names rather than trapping on its own.
|
|
8
9
|
import { executeCodec } from "./codecs.js";
|
|
10
|
+
import { actorIdentical } from "./actor-identity.js";
|
|
11
|
+
import { ActorWaits } from "./actor-waits.js";
|
|
9
12
|
import { TraceBuilder, runtimeFailure } from "../trace.js";
|
|
10
|
-
import { UNIT, cloneValue, toJsonValue } from "../values.js";
|
|
13
|
+
import { UNIT, cloneValue, stringValue, toJsonValue, } from "../values.js";
|
|
11
14
|
import { runtimeTag } from "./package.js";
|
|
12
15
|
import { StructuralRenderer } from "./structural.js";
|
|
16
|
+
import { timestampBinary } from "./timestamp.js";
|
|
13
17
|
import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
|
|
14
18
|
import { Pipes } from "./pipes.js";
|
|
15
19
|
import { FrameScheduler } from "./scheduler.js";
|
|
16
20
|
import { DeterministicIds } from "../../scheduler/ids.js";
|
|
17
21
|
import { Mt19937 } from "./mt19937.js";
|
|
18
|
-
import { ShimFault,
|
|
19
|
-
import {
|
|
22
|
+
import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
|
|
23
|
+
import { AdmittedPackage } from "./validate.js";
|
|
20
24
|
const DEFAULT_STEP_BUDGET = 1_000_000;
|
|
25
|
+
const DEFAULT_ACTOR_STOP_NS = 5_000_000_000;
|
|
21
26
|
/// Ends the run. The status is already recorded on the trace.
|
|
22
27
|
class Halt extends Error {
|
|
23
28
|
status;
|
|
@@ -52,7 +57,7 @@ class StdinReader {
|
|
|
52
57
|
return this.bytes.slice(start, this.offset);
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
export function runPackageV1(pkg, options = {}) {
|
|
60
|
+
export async function runPackageV1(pkg, options = {}) {
|
|
56
61
|
const fixtureId = options.fixtureId ?? "package";
|
|
57
62
|
const replay = {
|
|
58
63
|
seed: options.replay?.seed ?? 0,
|
|
@@ -65,14 +70,25 @@ export function runPackageV1(pkg, options = {}) {
|
|
|
65
70
|
const trace = new TraceBuilder(pkg, fixtureId, options.traceId ?? `trace:${fixtureId}`, replay, options.sandboxVersion ?? "0.0.0-spec");
|
|
66
71
|
// Admission is a load-time decision: nothing runs when the package names a
|
|
67
72
|
// capability the VM has no shim for.
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
trace.reject(
|
|
73
|
+
const admission = AdmittedPackage.load(pkg, options);
|
|
74
|
+
if (!(admission instanceof AdmittedPackage)) {
|
|
75
|
+
trace.reject(admission);
|
|
71
76
|
return trace.finish();
|
|
72
77
|
}
|
|
73
|
-
const
|
|
78
|
+
const directoryRefusal = await BrowserDirectories.admission(options.directoryRoots);
|
|
79
|
+
if (directoryRefusal) {
|
|
80
|
+
trace.reject({
|
|
81
|
+
category: "not_implemented",
|
|
82
|
+
code: "sandbox.host.directory",
|
|
83
|
+
capability: "directoryRoots",
|
|
84
|
+
message: directoryRefusal,
|
|
85
|
+
span: null,
|
|
86
|
+
});
|
|
87
|
+
return trace.finish();
|
|
88
|
+
}
|
|
89
|
+
const vm = new ExecutorV1(admission, trace, StdinReader.fromReplay(replay.inputs), options.schedulerPolicy ?? "round_robin", options);
|
|
74
90
|
try {
|
|
75
|
-
vm.run();
|
|
91
|
+
await vm.run();
|
|
76
92
|
}
|
|
77
93
|
catch (error) {
|
|
78
94
|
if (!(error instanceof Halt)) {
|
|
@@ -83,7 +99,6 @@ export function runPackageV1(pkg, options = {}) {
|
|
|
83
99
|
return trace.finish();
|
|
84
100
|
}
|
|
85
101
|
class ExecutorV1 {
|
|
86
|
-
pkg;
|
|
87
102
|
trace;
|
|
88
103
|
stdin;
|
|
89
104
|
options;
|
|
@@ -92,10 +107,13 @@ class ExecutorV1 {
|
|
|
92
107
|
scheduler;
|
|
93
108
|
ids;
|
|
94
109
|
actors = new Map();
|
|
110
|
+
actorWaits = new ActorWaits();
|
|
95
111
|
supervisors = new Map();
|
|
96
112
|
roles = new Map();
|
|
97
113
|
pipes = new Pipes();
|
|
98
114
|
generators = new Map();
|
|
115
|
+
generatorStreams = new Map();
|
|
116
|
+
senderEndpoints = new Map();
|
|
99
117
|
tasks = new Map();
|
|
100
118
|
completedTasks = 0;
|
|
101
119
|
actorIdentity = 0n;
|
|
@@ -105,18 +123,68 @@ class ExecutorV1 {
|
|
|
105
123
|
consumedResources = new WeakSet();
|
|
106
124
|
running = false;
|
|
107
125
|
rootComplete = false;
|
|
126
|
+
terminating = false;
|
|
108
127
|
rootFault = null;
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
pkg;
|
|
129
|
+
directories;
|
|
130
|
+
constructor(admission, trace, stdin, policy, options) {
|
|
111
131
|
this.trace = trace;
|
|
112
132
|
this.stdin = stdin;
|
|
113
133
|
this.options = options;
|
|
134
|
+
const pkg = admission.bytecode;
|
|
135
|
+
this.pkg = pkg;
|
|
136
|
+
this.directories = new BrowserDirectories(options.directoryRoots);
|
|
114
137
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
115
138
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
116
139
|
this.host = {
|
|
140
|
+
argv: [...(options.argv ?? ["main.hew"])],
|
|
141
|
+
unixTimeMillis: () => this.trace.virtualTimeMs,
|
|
142
|
+
calendarBridge: options.calendarBridge,
|
|
143
|
+
fileError: (operation) => {
|
|
144
|
+
const error = this.current.context.fileError;
|
|
145
|
+
switch (operation) {
|
|
146
|
+
case "HasError":
|
|
147
|
+
return { kind: "bool", value: error != null };
|
|
148
|
+
case "LastErrorKind":
|
|
149
|
+
return { kind: "i64", value: BigInt(error?.kind ?? 0) };
|
|
150
|
+
case "LastErrno":
|
|
151
|
+
if (error)
|
|
152
|
+
error.kind = 0;
|
|
153
|
+
return { kind: "i64", value: 0n };
|
|
154
|
+
case "LastError":
|
|
155
|
+
this.current.context.fileError = null;
|
|
156
|
+
return stringValue(error?.message ?? "");
|
|
157
|
+
}
|
|
158
|
+
},
|
|
117
159
|
pipes: this.pipes,
|
|
160
|
+
generatorStream: (value) => {
|
|
161
|
+
const id = this.ids.channel();
|
|
162
|
+
this.generatorStreams.set(id, {
|
|
163
|
+
generator: this.generatorFor(value),
|
|
164
|
+
closed: false,
|
|
165
|
+
pending: null,
|
|
166
|
+
fault: null,
|
|
167
|
+
receiving: false,
|
|
168
|
+
});
|
|
169
|
+
return { kind: "stream", channelId: id };
|
|
170
|
+
},
|
|
171
|
+
encoderSink: (sink, encoder) => this.senderEndpoint({
|
|
172
|
+
sink,
|
|
173
|
+
encoder,
|
|
174
|
+
references: 0,
|
|
175
|
+
endpoints: 0,
|
|
176
|
+
busy: false,
|
|
177
|
+
waiting: new Set(),
|
|
178
|
+
}, !this.sinkClosed(sink)),
|
|
179
|
+
cloneSink: (value) => {
|
|
180
|
+
const endpoint = value.kind === "sink"
|
|
181
|
+
? this.senderEndpoints.get(value.channelId)
|
|
182
|
+
: undefined;
|
|
183
|
+
return endpoint
|
|
184
|
+
? this.senderEndpoint(endpoint.owner, endpoint.open)
|
|
185
|
+
: this.pipes.cloneSink(value);
|
|
186
|
+
},
|
|
118
187
|
newPipe: (capacity) => this.newPipe(capacity),
|
|
119
|
-
closePipe: (value) => this.pipes.close(value, this.pipeFault(this.current)),
|
|
120
188
|
releaseValue: (value) => this.closeValue(value, this.pipeFault(this.current)),
|
|
121
189
|
writeStdout: (text) => this.trace.writeStdout(text, null),
|
|
122
190
|
writeStderr: (text) => this.trace.writeStderr(text, null),
|
|
@@ -135,7 +203,7 @@ class ExecutorV1 {
|
|
|
135
203
|
id: "actor:root",
|
|
136
204
|
actor: null,
|
|
137
205
|
returned: (value) => {
|
|
138
|
-
this.
|
|
206
|
+
this.completeRoot();
|
|
139
207
|
this.trace.exitCode =
|
|
140
208
|
pkg.entry?.exit === "status" && value.kind === "i64"
|
|
141
209
|
? Number(value.value)
|
|
@@ -143,36 +211,63 @@ class ExecutorV1 {
|
|
|
143
211
|
},
|
|
144
212
|
failed: (fault) => {
|
|
145
213
|
this.rootFault = fault;
|
|
146
|
-
this.
|
|
214
|
+
this.completeRoot();
|
|
147
215
|
},
|
|
148
216
|
});
|
|
149
217
|
}
|
|
150
218
|
/// One instruction or terminator executed is one step, ownership ops
|
|
151
219
|
/// included: the package's ops are what the program does.
|
|
152
|
-
run() {
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.
|
|
166
|
-
if (this.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.
|
|
220
|
+
async run() {
|
|
221
|
+
const root = this.current.context;
|
|
222
|
+
const cancel = () => {
|
|
223
|
+
const fault = {
|
|
224
|
+
kind: "panic",
|
|
225
|
+
message: "execution cancelled",
|
|
226
|
+
cancelled: true,
|
|
227
|
+
};
|
|
228
|
+
if (root.cancel)
|
|
229
|
+
root.cancel(fault);
|
|
230
|
+
else
|
|
231
|
+
root.pendingCancellation ??= fault;
|
|
232
|
+
};
|
|
233
|
+
this.options.signal?.addEventListener("abort", cancel, { once: true });
|
|
234
|
+
if (this.options.signal?.aborted)
|
|
235
|
+
cancel();
|
|
236
|
+
try {
|
|
237
|
+
this.runFrame(this.current);
|
|
238
|
+
await this.scheduler.run();
|
|
239
|
+
if (!this.rootComplete) {
|
|
240
|
+
throw new Error("the root frame is waiting with no runnable work");
|
|
241
|
+
}
|
|
242
|
+
// Native main return drains accepted work before closing idle actors.
|
|
243
|
+
for (const owner of this.supervisors.values())
|
|
244
|
+
if (!owner.supervisor)
|
|
245
|
+
this.stopSupervisor(owner);
|
|
246
|
+
for (const actor of this.actors.values())
|
|
247
|
+
if (!actor.supervisor && actor.alive)
|
|
248
|
+
this.requestActorStop(actor);
|
|
249
|
+
await this.scheduler.run();
|
|
250
|
+
if (this.rootFault)
|
|
251
|
+
this.haltWithFault(this.rootFault);
|
|
252
|
+
if (this.trace.exitCode === 0 && this.faultDebts.size)
|
|
253
|
+
this.trace.exitCode = 1;
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
this.terminating = true;
|
|
257
|
+
await this.scheduler.retireHost();
|
|
258
|
+
this.directories.retire((value) => this.closeHostResource(value));
|
|
259
|
+
this.options.signal?.removeEventListener("abort", cancel);
|
|
260
|
+
}
|
|
170
261
|
}
|
|
171
262
|
runFrame(frame) {
|
|
172
263
|
this.current = frame;
|
|
173
264
|
this.running = true;
|
|
174
265
|
while (this.running) {
|
|
175
266
|
const act = this.current;
|
|
267
|
+
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
268
|
+
if (this.scopeDeadline(act, fault) !== fault)
|
|
269
|
+
act.context.pendingCancellation ??= fault;
|
|
270
|
+
};
|
|
176
271
|
while (act.opIndex < act.block.ops.length) {
|
|
177
272
|
const op = act.block.ops[act.opIndex++];
|
|
178
273
|
if (op.op !== "coverage.hit")
|
|
@@ -203,9 +298,19 @@ class ExecutorV1 {
|
|
|
203
298
|
}
|
|
204
299
|
// ── frames ───────────────────────────────────────────────────────────────
|
|
205
300
|
activate(fn, args, caller, result, normal, unwind, context) {
|
|
206
|
-
|
|
207
|
-
|
|
301
|
+
const mandatoryClose = (this.pkg.resources ?? []).some((resource) => "close" in resource && resource.close === fn.id);
|
|
302
|
+
const parentContext = caller?.context ?? context;
|
|
303
|
+
parentContext.pollBudget ??= { remaining: 0 };
|
|
304
|
+
if (mandatoryClose && args[0])
|
|
208
305
|
this.closingResources.add(args[0]);
|
|
306
|
+
const executionContext = mandatoryClose
|
|
307
|
+
? {
|
|
308
|
+
...parentContext,
|
|
309
|
+
cancel: undefined,
|
|
310
|
+
pendingCancellation: undefined,
|
|
311
|
+
requestDeadline: undefined,
|
|
312
|
+
}
|
|
313
|
+
: parentContext;
|
|
209
314
|
const env = new Map();
|
|
210
315
|
for (const [at, param] of fn.params.entries()) {
|
|
211
316
|
env.set(param.value, ownedRef(args[at] ?? UNIT));
|
|
@@ -220,7 +325,7 @@ class ExecutorV1 {
|
|
|
220
325
|
for (const block of fn.blocks) {
|
|
221
326
|
block.params.forEach(record);
|
|
222
327
|
for (const op of block.ops) {
|
|
223
|
-
if (op.dst != null && op.own === "none")
|
|
328
|
+
if ("dst" in op && op.dst != null && op.own === "none")
|
|
224
329
|
trivial.add(op.dst);
|
|
225
330
|
}
|
|
226
331
|
const term = block.term;
|
|
@@ -231,7 +336,13 @@ class ExecutorV1 {
|
|
|
231
336
|
arm.fields.forEach(record);
|
|
232
337
|
}
|
|
233
338
|
return {
|
|
234
|
-
|
|
339
|
+
rcOwner: fn.actor_construction
|
|
340
|
+
? { blocks: new Set(), closed: false }
|
|
341
|
+
: (caller?.rcOwner ??
|
|
342
|
+
executionContext.rcOwner ??
|
|
343
|
+
executionContext.actor?.rcOwner ??
|
|
344
|
+
null),
|
|
345
|
+
context: executionContext,
|
|
235
346
|
fn,
|
|
236
347
|
block: this.blockAt(fn, fn.entry),
|
|
237
348
|
opIndex: 0,
|
|
@@ -243,7 +354,7 @@ class ExecutorV1 {
|
|
|
243
354
|
parked: [],
|
|
244
355
|
caller,
|
|
245
356
|
result,
|
|
246
|
-
normal,
|
|
357
|
+
normal: normal ?? undefined,
|
|
247
358
|
unwind,
|
|
248
359
|
};
|
|
249
360
|
}
|
|
@@ -373,7 +484,7 @@ class ExecutorV1 {
|
|
|
373
484
|
this.define(act, op.dst, { kind: "f64", value: floatConst(op) });
|
|
374
485
|
return;
|
|
375
486
|
case "const.char":
|
|
376
|
-
this.define(act, op.dst,
|
|
487
|
+
this.define(act, op.dst, stringValue(op.value));
|
|
377
488
|
return;
|
|
378
489
|
case "const.unit":
|
|
379
490
|
this.define(act, op.dst, UNIT);
|
|
@@ -382,10 +493,7 @@ class ExecutorV1 {
|
|
|
382
493
|
this.define(act, op.dst, { kind: "i64", value: BigInt(op.nanos) });
|
|
383
494
|
return;
|
|
384
495
|
case "const.str":
|
|
385
|
-
this.define(act, op.dst,
|
|
386
|
-
kind: "string",
|
|
387
|
-
value: this.pkg.strings[op.str] ?? "",
|
|
388
|
-
});
|
|
496
|
+
this.define(act, op.dst, stringValue(this.pkg.strings[op.str] ?? ""));
|
|
389
497
|
return;
|
|
390
498
|
case "const.bytes":
|
|
391
499
|
this.define(act, op.dst, bytesValue(this.pkg.bytes[op.bytes] ?? []));
|
|
@@ -459,7 +567,7 @@ class ExecutorV1 {
|
|
|
459
567
|
case "aggregate.make":
|
|
460
568
|
this.define(act, op.dst, {
|
|
461
569
|
kind: "record",
|
|
462
|
-
typeId: this.aggregateName(op.shape),
|
|
570
|
+
typeId: op.shape === null ? "" : this.aggregateName(op.shape),
|
|
463
571
|
shape: op.shape ?? undefined,
|
|
464
572
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
465
573
|
});
|
|
@@ -652,8 +760,9 @@ class ExecutorV1 {
|
|
|
652
760
|
const context = {
|
|
653
761
|
id,
|
|
654
762
|
actor: act.context.actor,
|
|
763
|
+
rcOwner: act.rcOwner,
|
|
655
764
|
requestDeadline: this.requestDeadline(act),
|
|
656
|
-
returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault)),
|
|
765
|
+
returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault), act.context.actor, act.rcOwner),
|
|
657
766
|
failed: (fault) => this.finishGenerator(generator, fault),
|
|
658
767
|
};
|
|
659
768
|
const generator = {
|
|
@@ -748,10 +857,10 @@ class ExecutorV1 {
|
|
|
748
857
|
changed();
|
|
749
858
|
};
|
|
750
859
|
if (failure && winner)
|
|
751
|
-
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor);
|
|
860
|
+
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor, act.rcOwner);
|
|
752
861
|
else
|
|
753
862
|
finish();
|
|
754
|
-
}, act.context.actor);
|
|
863
|
+
}, act.context.actor, act.rcOwner);
|
|
755
864
|
};
|
|
756
865
|
task.cancel = () => {
|
|
757
866
|
if (task.done || task.finishing)
|
|
@@ -770,12 +879,44 @@ class ExecutorV1 {
|
|
|
770
879
|
ready();
|
|
771
880
|
return;
|
|
772
881
|
}
|
|
773
|
-
case "task.spawn":
|
|
882
|
+
case "task.spawn":
|
|
883
|
+
case "task.invoke": {
|
|
774
884
|
const group = act.scopes.get(op.scope);
|
|
775
885
|
if (!group)
|
|
776
886
|
throw new Error(`task scope ${op.scope} is missing`);
|
|
777
|
-
const
|
|
778
|
-
|
|
887
|
+
const consuming = op.op === "task.spawn" ||
|
|
888
|
+
("receiver" in op.callee && op.callee.receiver.decision === "move");
|
|
889
|
+
const dynamic = op.op === "task.invoke" && "dyn" in op.callee ? op.callee : null;
|
|
890
|
+
const dynamicReceiver = dynamic
|
|
891
|
+
? this.boundary(act, dynamic.receiver)
|
|
892
|
+
: null;
|
|
893
|
+
if (dynamicReceiver && dynamicReceiver.kind !== "dyn")
|
|
894
|
+
throw new TypeError("prepared dynamic receiver is not a trait object");
|
|
895
|
+
const dynamicEntry = dynamicReceiver?.kind === "dyn" && dynamic
|
|
896
|
+
? this.dynamicEntry(dynamicReceiver, dynamic.dyn)
|
|
897
|
+
: null;
|
|
898
|
+
if (dynamic && dynamicEntry?.method !== dynamic.method)
|
|
899
|
+
throw new Error("prepared dynamic slot differs from its checked method");
|
|
900
|
+
const callable = op.op === "task.spawn"
|
|
901
|
+
? this.read(act, op.callable)
|
|
902
|
+
: "direct" in op.callee
|
|
903
|
+
? { kind: "function", id: String(op.callee.direct) }
|
|
904
|
+
: "mutable" in op.callee
|
|
905
|
+
? { kind: "function", id: String(op.callee.mutable) }
|
|
906
|
+
: "dyn" in op.callee
|
|
907
|
+
? {
|
|
908
|
+
kind: "function",
|
|
909
|
+
id: String(dynamicEntry.callee),
|
|
910
|
+
}
|
|
911
|
+
: this.boundary(act, op.callee.receiver);
|
|
912
|
+
if (op.op === "task.spawn")
|
|
913
|
+
this.invalidate(act, op.callable);
|
|
914
|
+
const args = op.op === "task.invoke"
|
|
915
|
+
? op.args.map((arg) => this.boundary(act, arg))
|
|
916
|
+
: [];
|
|
917
|
+
const specialization = op.op === "task.invoke" && "specialization" in op.callee
|
|
918
|
+
? op.callee.specialization
|
|
919
|
+
: null;
|
|
779
920
|
const task = this.newTask();
|
|
780
921
|
group.tasks.push(task);
|
|
781
922
|
const settle = (value, fault) => {
|
|
@@ -786,9 +927,7 @@ class ExecutorV1 {
|
|
|
786
927
|
!(fault.kind === "panic" && (fault.cancelled || fault.deadline)) &&
|
|
787
928
|
act.context.actor)
|
|
788
929
|
act.context.actor.crashing ??= fault;
|
|
789
|
-
|
|
790
|
-
? this.faultText(act.context.actor.crashing)
|
|
791
|
-
: null, (closeFault) => {
|
|
930
|
+
const complete = (closeFault) => {
|
|
792
931
|
if (!fault || (fault.kind === "panic" && fault.cancelled))
|
|
793
932
|
fault = closeFault ?? fault;
|
|
794
933
|
this.settleTask(task, value, fault);
|
|
@@ -803,41 +942,104 @@ class ExecutorV1 {
|
|
|
803
942
|
}
|
|
804
943
|
for (const changed of [...group.changed])
|
|
805
944
|
changed();
|
|
806
|
-
}
|
|
945
|
+
};
|
|
946
|
+
if (!consuming) {
|
|
947
|
+
complete(null);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
this.closeValueAsync(callable, act.context.actor
|
|
951
|
+
? this.faultText(act.context.actor.crashing)
|
|
952
|
+
: null, complete, act.context.actor, act.rcOwner);
|
|
953
|
+
};
|
|
954
|
+
const mutable = op.op === "task.invoke" && "mutable" in op.callee ? op.callee : null;
|
|
955
|
+
const receiverRef = mutable
|
|
956
|
+
? this.refOf(act, mutable.receiver.value)
|
|
957
|
+
: null;
|
|
958
|
+
if (dynamicReceiver?.kind === "dyn" && dynamicEntry && dynamic)
|
|
959
|
+
args.unshift(this.erasedReceiver(act, dynamic.receiver, dynamicReceiver.value, dynamicEntry.receiver));
|
|
960
|
+
if (receiverRef)
|
|
961
|
+
args.unshift(mutable.staged
|
|
962
|
+
? cloneValue(readRef(receiverRef))
|
|
963
|
+
: readRef(receiverRef));
|
|
964
|
+
const handback = (value, fault, result) => {
|
|
965
|
+
if (!receiverRef || !mutable || value === null)
|
|
966
|
+
throw new Error("prepared mutable method omitted its receiver handback");
|
|
967
|
+
if (mutable.staged && fault) {
|
|
968
|
+
this.closeValueAsync(value, this.faultText(fault), (closeFault) => settle(UNIT, combineFaults(fault, closeFault)), act.context.actor, act.rcOwner);
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
const previous = readRef(receiverRef);
|
|
972
|
+
writeRef(receiverRef, value);
|
|
973
|
+
if (mutable.staged)
|
|
974
|
+
this.closeValueAsync(previous, null, (closeFault) => {
|
|
975
|
+
if (closeFault)
|
|
976
|
+
this.closeValueAsync(result, this.faultText(closeFault), (resultFault) => settle(UNIT, combineFaults(closeFault, resultFault)), act.context.actor, act.rcOwner);
|
|
977
|
+
else
|
|
978
|
+
settle(result, null);
|
|
979
|
+
}, act.context.actor, act.rcOwner);
|
|
980
|
+
else
|
|
981
|
+
settle(result, fault);
|
|
982
|
+
}
|
|
807
983
|
};
|
|
808
984
|
const context = {
|
|
809
985
|
id: task.id,
|
|
810
986
|
actor: act.context.actor,
|
|
987
|
+
rcOwner: act.rcOwner,
|
|
811
988
|
requestDeadline: this.requestDeadline(act),
|
|
812
|
-
returned: (value) =>
|
|
813
|
-
|
|
989
|
+
returned: (value) => {
|
|
990
|
+
if (dynamicEntry?.receiver_handback &&
|
|
991
|
+
dynamicReceiver?.kind === "dyn") {
|
|
992
|
+
if (value.kind !== "record" || value.fields.length !== 2)
|
|
993
|
+
throw new Error("prepared dynamic method omitted its dual result");
|
|
994
|
+
dynamicReceiver.value = value.fields[1];
|
|
995
|
+
settle(value.fields[0], null);
|
|
996
|
+
}
|
|
997
|
+
else if (!mutable)
|
|
998
|
+
settle(value, null);
|
|
999
|
+
else if (value.kind === "record" && value.fields.length === 2)
|
|
1000
|
+
handback(value.fields[1], null, value.fields[0]);
|
|
1001
|
+
else
|
|
1002
|
+
throw new Error("prepared mutable method returned no dual result");
|
|
1003
|
+
},
|
|
1004
|
+
failed: (fault, value) => {
|
|
1005
|
+
if (dynamicEntry?.receiver_handback &&
|
|
1006
|
+
dynamicReceiver?.kind === "dyn") {
|
|
1007
|
+
if (!value)
|
|
1008
|
+
throw new Error("prepared dynamic method omitted its fault handback");
|
|
1009
|
+
dynamicReceiver.value = value;
|
|
1010
|
+
}
|
|
1011
|
+
if (mutable)
|
|
1012
|
+
handback(value ?? null, fault, UNIT);
|
|
1013
|
+
else
|
|
1014
|
+
settle(UNIT, fault);
|
|
1015
|
+
},
|
|
814
1016
|
};
|
|
815
|
-
const frame = this.activate(this.functionAt(calleeFunction(callable)), callable.kind === "closure" ? [callable.environment] :
|
|
816
|
-
let started = false;
|
|
1017
|
+
const frame = this.activate(this.functionAt(calleeFunction(callable, specialization)), callable.kind === "closure" ? [callable.environment, ...args] : args, null, null, undefined, null, context);
|
|
817
1018
|
task.cancel = () => {
|
|
818
1019
|
if (task.done || task.finishing)
|
|
819
1020
|
return;
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
cancelled: true,
|
|
827
|
-
});
|
|
1021
|
+
context.pendingCancellation ??= {
|
|
1022
|
+
kind: "panic",
|
|
1023
|
+
message: "task cancelled",
|
|
1024
|
+
cancelled: true,
|
|
1025
|
+
};
|
|
1026
|
+
context.cancel?.(context.pendingCancellation);
|
|
828
1027
|
};
|
|
829
1028
|
this.scheduler.enqueue(task.id, () => {
|
|
830
1029
|
if (task.done || task.finishing)
|
|
831
1030
|
return;
|
|
832
|
-
started = true;
|
|
833
1031
|
this.runFrame(frame);
|
|
834
1032
|
});
|
|
835
1033
|
this.trace.allocateId("task", task.id, act.context.id);
|
|
836
1034
|
this.define(act, op.dst, { kind: "task", id: task.id });
|
|
837
1035
|
return;
|
|
838
1036
|
}
|
|
839
|
-
|
|
840
|
-
throw new Error(
|
|
1037
|
+
case "actor.ingress_adapter":
|
|
1038
|
+
throw new Error("actor ingress adapters are not admitted in the VM");
|
|
1039
|
+
default: {
|
|
1040
|
+
const exhaustive = op;
|
|
1041
|
+
throw new Error("unhandled instruction: " + String(exhaustive));
|
|
1042
|
+
}
|
|
841
1043
|
}
|
|
842
1044
|
}
|
|
843
1045
|
/// Hand each field of a consumed container, or each payload of a matched
|
|
@@ -887,6 +1089,20 @@ class ExecutorV1 {
|
|
|
887
1089
|
case "return":
|
|
888
1090
|
this.returnFrom(act, term.value ? this.boundary(act, term.value) : UNIT);
|
|
889
1091
|
return;
|
|
1092
|
+
case "timestamp.binary": {
|
|
1093
|
+
const outcome = timestampBinary(this.pkg, term, this.read(act, term.lhs), this.read(act, term.rhs));
|
|
1094
|
+
if (outcome === null) {
|
|
1095
|
+
const failure = term.failures.find((edge) => edge.trap === "integer_overflow");
|
|
1096
|
+
if (!failure)
|
|
1097
|
+
throw new Error("timestamp arithmetic lacks its overflow edge");
|
|
1098
|
+
this.takeEdge(act, failure.edge);
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
this.define(act, term.result.value, outcome);
|
|
1102
|
+
this.takeEdge(act, term.normal);
|
|
1103
|
+
}
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
890
1106
|
case "checked.binary": {
|
|
891
1107
|
const failure = checkedFailure(term.binary_op, this.read(act, term.lhs), this.read(act, term.rhs), term.ty);
|
|
892
1108
|
if (failure) {
|
|
@@ -906,14 +1122,7 @@ class ExecutorV1 {
|
|
|
906
1122
|
if (receiver.kind !== "dyn") {
|
|
907
1123
|
throw new TypeError(`dyn.call expected a trait object, got ${receiver.kind}`);
|
|
908
1124
|
}
|
|
909
|
-
const
|
|
910
|
-
this.pkg.vtables.find((candidate) => candidate.id === receiver.vtable);
|
|
911
|
-
// The slot number is the checker's own index, so it is matched rather
|
|
912
|
-
// than used as a position in the slot array.
|
|
913
|
-
const entry = vtable?.slots.find((candidate) => candidate.slot === term.slot);
|
|
914
|
-
if (!entry) {
|
|
915
|
-
throw new Error(`vtable ${receiver.vtable} has no slot ${term.slot}`);
|
|
916
|
-
}
|
|
1125
|
+
const entry = this.dynamicEntry(receiver, term.slot);
|
|
917
1126
|
// The wrapped value is the method's receiver, ahead of the call's own
|
|
918
1127
|
// arguments, passed under the decision the slot records: the erasure
|
|
919
1128
|
// the concrete type was made under, not something read off the method.
|
|
@@ -946,102 +1155,128 @@ class ExecutorV1 {
|
|
|
946
1155
|
return;
|
|
947
1156
|
case "runtime.call": {
|
|
948
1157
|
const entry = this.pkg.runtime_families[term.family];
|
|
949
|
-
if (entry
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const tag = runtimeTag(shape, "CallerActor");
|
|
955
|
-
payload.push({
|
|
956
|
-
kind: "record",
|
|
957
|
-
typeId: shape.cases[tag].fields[0],
|
|
958
|
-
fields: [{ kind: "i64", value: caller.identity }],
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
this.completeShim(act, term, this.variant(term.result_shape, caller.kind === "Actor"
|
|
962
|
-
? "CallerActor"
|
|
963
|
-
: caller.kind === "Timer"
|
|
964
|
-
? "CallerTimer"
|
|
965
|
-
: "CallerTask", payload));
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
if (entry?.family === "ActorRefusalLog") {
|
|
969
|
-
const [message, handler, reason] = term.args.map((argument) => this.boundary(act, argument));
|
|
970
|
-
this.trace.snapshot("actor.delivery_refused", {
|
|
971
|
-
actor_id: act.context.actor?.id ?? null,
|
|
972
|
-
handler: toJsonValue(handler),
|
|
973
|
-
message: toJsonValue(message),
|
|
974
|
-
reason: toJsonValue(reason),
|
|
975
|
-
});
|
|
976
|
-
if (!act.context.reply)
|
|
977
|
-
this.trace.writeStderr(`WARN actor.delivery_refused actor=${act.context.actor?.identity ?? 0n} handler=${asString(handler)} reason=${JSON.stringify(asString(reason))}\n`, null);
|
|
978
|
-
this.completeShim(act, term, UNIT);
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
if (entry &&
|
|
982
|
-
(entry.family === "Map" ||
|
|
983
|
-
entry.family === "Set" ||
|
|
984
|
-
(entry.family === "Array" && entry.detail === "Set") ||
|
|
985
|
-
(entry.family === "Vector" &&
|
|
986
|
-
["Contains", "Clear", "Set"].includes(String(entry.detail))))) {
|
|
987
|
-
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
988
|
-
const iterator = collectionOperation(this.pkg, entry.family === "Array" ? "Vector" : entry.family, String(entry.detail), args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape);
|
|
989
|
-
this.runValueProgram(act, term, args, iterator);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
if (entry &&
|
|
993
|
-
[
|
|
994
|
-
"RcNew",
|
|
995
|
-
"RcClone",
|
|
996
|
-
"RcGet",
|
|
997
|
-
"RcIsUnique",
|
|
998
|
-
"RcSet",
|
|
999
|
-
"RcStrongCount",
|
|
1000
|
-
"RcWeakCount",
|
|
1001
|
-
].includes(entry.family)) {
|
|
1002
|
-
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1003
|
-
this.runValueProgram(act, term, args, sharedOperation(entry.family, args));
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
if (entry?.family === "SupervisorPool") {
|
|
1007
|
-
this.supervisorPool(act, term, String(entry.detail));
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
if (entry && ["SinkClose", "StreamClose"].includes(entry.family)) {
|
|
1011
|
-
const value = this.boundary(act, term.args[0]);
|
|
1012
|
-
this.running = false;
|
|
1013
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
1014
|
-
if (fault)
|
|
1015
|
-
this.raiseFault(act, fault, term.unwind);
|
|
1016
|
-
else
|
|
1017
|
-
this.completeShim(act, term, UNIT);
|
|
1018
|
-
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1019
|
-
});
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
if (entry?.family === "StructuralFormat") {
|
|
1023
|
-
this.structuralFormat(act, term);
|
|
1158
|
+
if (!entry)
|
|
1159
|
+
throw new Error("runtime family is absent after admission");
|
|
1160
|
+
const registration = resolveRuntime(entry);
|
|
1161
|
+
if (registration.kind === "ordinary") {
|
|
1162
|
+
this.callShim(act, term, registration.shim);
|
|
1024
1163
|
return;
|
|
1025
1164
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1165
|
+
if (registration.kind === "refused")
|
|
1166
|
+
throw new Error(`runtime family ${registration.capability} was refused after admission`);
|
|
1167
|
+
switch (registration.handler) {
|
|
1168
|
+
case "actor_caller": {
|
|
1169
|
+
const caller = act.context.caller ?? { kind: "Task" };
|
|
1170
|
+
const payload = [];
|
|
1171
|
+
if (caller.kind === "Actor") {
|
|
1172
|
+
const shape = this.pkg.variants[term.result_shape];
|
|
1173
|
+
const tag = runtimeTag(shape, "CallerActor");
|
|
1174
|
+
payload.push({
|
|
1175
|
+
kind: "record",
|
|
1176
|
+
typeId: shape.cases[tag].fields[0],
|
|
1177
|
+
fields: [{ kind: "i64", value: caller.identity }],
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
this.completeShim(act, term, this.variant(term.result_shape, caller.kind === "Actor"
|
|
1181
|
+
? "CallerActor"
|
|
1182
|
+
: caller.kind === "Timer"
|
|
1183
|
+
? "CallerTimer"
|
|
1184
|
+
: "CallerTask", payload));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
case "actor_refusal_log": {
|
|
1188
|
+
const [message, handler, reason] = term.args.map((argument) => this.boundary(act, argument));
|
|
1189
|
+
this.trace.snapshot("actor.delivery_refused", {
|
|
1190
|
+
actor_id: act.context.actor?.id ?? null,
|
|
1191
|
+
handler: toJsonValue(handler),
|
|
1192
|
+
message: toJsonValue(message),
|
|
1193
|
+
reason: toJsonValue(reason),
|
|
1194
|
+
});
|
|
1195
|
+
if (!act.context.reply)
|
|
1196
|
+
this.trace.writeStderr(`WARN actor.delivery_refused actor=${act.context.actor?.identity ?? 0n} handler=${asString(handler)} reason=${JSON.stringify(asString(reason))}\n`, null);
|
|
1197
|
+
this.completeShim(act, term, UNIT);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
case "collection": {
|
|
1201
|
+
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1202
|
+
const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape);
|
|
1203
|
+
this.runValueProgram(act, term, args, iterator);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
case "shared": {
|
|
1207
|
+
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1208
|
+
this.runValueProgram(act, term, args, sharedOperation(registration.operation, args, act.rcOwner, term.shared_payload_type ?? "", (value) => this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : [])));
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
case "supervisor_pool": {
|
|
1212
|
+
this.supervisorPool(act, term, registration.operation);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
case "close": {
|
|
1216
|
+
const value = this.boundary(act, term.args[0]);
|
|
1217
|
+
this.running = false;
|
|
1218
|
+
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
1219
|
+
if (fault)
|
|
1220
|
+
this.raiseFault(act, fault, term.unwind);
|
|
1221
|
+
else
|
|
1222
|
+
this.completeShim(act, term, UNIT);
|
|
1223
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1224
|
+
}, act.context.actor, act.rcOwner);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
case "structural_format": {
|
|
1228
|
+
this.structuralFormat(act, term);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
case "utf8_decode": {
|
|
1232
|
+
const value = decodeUtf8Checked(this.boundary(act, term.args[0]));
|
|
1233
|
+
let result;
|
|
1234
|
+
if ("value" in value)
|
|
1235
|
+
result = this.variant(term.result_shape, "ResultOk", [
|
|
1236
|
+
stringValue(value.value),
|
|
1237
|
+
]);
|
|
1238
|
+
else {
|
|
1239
|
+
const shapes = term.variant_result_shapes;
|
|
1240
|
+
const error = this.pkg.aggregates[shapes.error];
|
|
1241
|
+
result = this.variant(term.result_shape, "ResultErr", [
|
|
1242
|
+
{
|
|
1243
|
+
kind: "record",
|
|
1244
|
+
typeId: error.name,
|
|
1245
|
+
shape: error.id,
|
|
1246
|
+
fields: [
|
|
1247
|
+
{ kind: "i64", value: BigInt(value.validUpTo) },
|
|
1248
|
+
this.variant(shapes.error_len, value.errorLen === null ? "OptionNone" : "OptionSome", value.errorLen === null
|
|
1249
|
+
? []
|
|
1250
|
+
: [{ kind: "i64", value: BigInt(value.errorLen) }]),
|
|
1251
|
+
],
|
|
1252
|
+
},
|
|
1253
|
+
]);
|
|
1254
|
+
}
|
|
1255
|
+
this.completeShim(act, term, result);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
default: {
|
|
1259
|
+
const exhaustive = registration;
|
|
1260
|
+
throw new Error(`unknown runtime continuation ${String(exhaustive)}`);
|
|
1261
|
+
}
|
|
1029
1262
|
}
|
|
1030
|
-
this.callShim(act, term, shim);
|
|
1031
|
-
return;
|
|
1032
1263
|
}
|
|
1033
1264
|
case "extern.call": {
|
|
1034
1265
|
const entry = this.pkg.externs[term.extern];
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1266
|
+
if (!entry)
|
|
1267
|
+
throw new Error("external operation is absent after admission");
|
|
1268
|
+
const registration = resolveExtern(entry);
|
|
1269
|
+
if (registration.kind === "refused")
|
|
1270
|
+
throw new Error(`external operation ${registration.capability} was refused after admission`);
|
|
1271
|
+
if (registration.kind === "directory")
|
|
1272
|
+
this.callDirectory(act, term, registration.operation);
|
|
1273
|
+
else
|
|
1274
|
+
this.callShim(act, term, registration.shim);
|
|
1040
1275
|
return;
|
|
1041
1276
|
}
|
|
1042
1277
|
case "wire.codec": {
|
|
1043
1278
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1044
|
-
this.runValueProgram(act, term, args, executeCodec(this.pkg, term, args[0], this.options.
|
|
1279
|
+
this.runValueProgram(act, term, args, executeCodec(this.pkg, term, args[0], this.options[CODEC_REGISTRATION.bridge], this.options.propertyInput ?? []));
|
|
1045
1280
|
return;
|
|
1046
1281
|
}
|
|
1047
1282
|
case "value.call": {
|
|
@@ -1081,6 +1316,7 @@ class ExecutorV1 {
|
|
|
1081
1316
|
this.takeEdge(act, act.fault ? term.fault : term.normal);
|
|
1082
1317
|
return;
|
|
1083
1318
|
case "enter_defer":
|
|
1319
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) + 1;
|
|
1084
1320
|
// The deferred body runs on its own fault state: a body reached while
|
|
1085
1321
|
// unwinding must not read the fault it is cleaning up after.
|
|
1086
1322
|
act.parked.push(act.fault);
|
|
@@ -1088,6 +1324,7 @@ class ExecutorV1 {
|
|
|
1088
1324
|
this.takeEdge(act, term.body);
|
|
1089
1325
|
return;
|
|
1090
1326
|
case "finish_defer": {
|
|
1327
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) - 1;
|
|
1091
1328
|
// Combine the parked fault with whatever the body left, keeping the
|
|
1092
1329
|
// first: a join must never forget the fault the boundary carried in.
|
|
1093
1330
|
const parked = act.parked.pop();
|
|
@@ -1117,12 +1354,7 @@ class ExecutorV1 {
|
|
|
1117
1354
|
typeId: shape.name,
|
|
1118
1355
|
shape: shape.id,
|
|
1119
1356
|
tag: deadline ? term.deadline_variant : term.fault_variant,
|
|
1120
|
-
payload: [
|
|
1121
|
-
{
|
|
1122
|
-
kind: "string",
|
|
1123
|
-
value: faultReport(fault),
|
|
1124
|
-
},
|
|
1125
|
-
],
|
|
1357
|
+
payload: [stringValue(faultReport(fault))],
|
|
1126
1358
|
};
|
|
1127
1359
|
act.fault = null;
|
|
1128
1360
|
if (act.context.actor)
|
|
@@ -1155,6 +1387,29 @@ class ExecutorV1 {
|
|
|
1155
1387
|
act.block = target;
|
|
1156
1388
|
act.opIndex = 0;
|
|
1157
1389
|
}
|
|
1390
|
+
callDirectory(act, term, operation) {
|
|
1391
|
+
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1392
|
+
const result = this.directories.call(operation, args, term.result_resource);
|
|
1393
|
+
if (!(result instanceof Promise)) {
|
|
1394
|
+
this.completeShim(act, term, result);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
// Acquisitions are synchronous in the language. Retain borrowed inputs and
|
|
1398
|
+
// publish the new owner before the next authored cancellation safepoint.
|
|
1399
|
+
this.running = false;
|
|
1400
|
+
act.context.parked = true;
|
|
1401
|
+
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1402
|
+
act.context.pendingCancellation ??= fault;
|
|
1403
|
+
};
|
|
1404
|
+
this.scheduler.submitHost(act.context.id, result, (outcome) => {
|
|
1405
|
+
act.context.parked = false;
|
|
1406
|
+
act.context.fileError = outcome.error;
|
|
1407
|
+
this.completeShim(act, term, outcome.value);
|
|
1408
|
+
this.runFrame(act);
|
|
1409
|
+
}, (error) => {
|
|
1410
|
+
throw error;
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1158
1413
|
/// Run a runtime or extern shim. A fault it raises takes the call's `unwind`
|
|
1159
1414
|
/// edge so the cleanup the package names runs before the program ends.
|
|
1160
1415
|
callShim(act, term, shim) {
|
|
@@ -1168,7 +1423,9 @@ class ExecutorV1 {
|
|
|
1168
1423
|
: (this.pkg.variants[shapeId] ?? null);
|
|
1169
1424
|
let value;
|
|
1170
1425
|
try {
|
|
1171
|
-
|
|
1426
|
+
const aggregateId = term.aggregate_result_shape;
|
|
1427
|
+
const aggregate = aggregateId == null ? null : (this.pkg.aggregates[aggregateId] ?? null);
|
|
1428
|
+
value = shim(this.host, args, shape, aggregate);
|
|
1172
1429
|
}
|
|
1173
1430
|
catch (error) {
|
|
1174
1431
|
if (error instanceof ShimFault) {
|
|
@@ -1217,7 +1474,7 @@ class ExecutorV1 {
|
|
|
1217
1474
|
this.keepInputs(act, term, args);
|
|
1218
1475
|
this.raiseFault(act, fault, term.unwind);
|
|
1219
1476
|
resume();
|
|
1220
|
-
});
|
|
1477
|
+
}, act.context.actor, act.rcOwner);
|
|
1221
1478
|
};
|
|
1222
1479
|
const next = (value = UNIT) => {
|
|
1223
1480
|
let step;
|
|
@@ -1235,6 +1492,9 @@ class ExecutorV1 {
|
|
|
1235
1492
|
this.completeShim(act, term, step.value);
|
|
1236
1493
|
resume();
|
|
1237
1494
|
}
|
|
1495
|
+
else if (step.value.kind === "fault") {
|
|
1496
|
+
fail({ kind: "panic", message: step.value.message });
|
|
1497
|
+
}
|
|
1238
1498
|
else if (step.value.kind === "release") {
|
|
1239
1499
|
const request = step.value;
|
|
1240
1500
|
this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
|
|
@@ -1248,11 +1508,11 @@ class ExecutorV1 {
|
|
|
1248
1508
|
act.fault ??= fault;
|
|
1249
1509
|
}
|
|
1250
1510
|
next();
|
|
1251
|
-
});
|
|
1511
|
+
}, act.context.actor, act.rcOwner);
|
|
1252
1512
|
}
|
|
1253
1513
|
else {
|
|
1254
1514
|
const request = step.value;
|
|
1255
|
-
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail);
|
|
1515
|
+
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner);
|
|
1256
1516
|
act.context.cancel = (fault) => child.cancel?.(fault);
|
|
1257
1517
|
}
|
|
1258
1518
|
};
|
|
@@ -1261,18 +1521,35 @@ class ExecutorV1 {
|
|
|
1261
1521
|
}
|
|
1262
1522
|
supervisorPool(act, term, operation) {
|
|
1263
1523
|
const [view, index] = term.args.map((arg) => this.boundary(act, arg));
|
|
1264
|
-
if (view?.kind !== "vector"
|
|
1265
|
-
throw new Error("pool operation has no view
|
|
1524
|
+
if (view?.kind !== "vector")
|
|
1525
|
+
throw new Error("pool operation has no view");
|
|
1526
|
+
if (operation === "Len") {
|
|
1527
|
+
this.completeShim(act, term, {
|
|
1528
|
+
kind: "i64",
|
|
1529
|
+
value: BigInt(view.items.length),
|
|
1530
|
+
});
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
if (index?.kind !== "i64")
|
|
1534
|
+
throw new Error("pool operation has no index");
|
|
1266
1535
|
const value = view.items[Number(index.value)];
|
|
1267
1536
|
if (operation === "Get") {
|
|
1268
1537
|
this.completeShim(act, term, this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : []));
|
|
1269
1538
|
return;
|
|
1270
1539
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1540
|
+
switch (operation) {
|
|
1541
|
+
case "Member":
|
|
1542
|
+
if (!value) {
|
|
1543
|
+
this.raiseFault(act, { kind: "trap", trap: "vector_bounds" }, term.unwind);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
this.completeShim(act, term, value);
|
|
1547
|
+
return;
|
|
1548
|
+
default: {
|
|
1549
|
+
const exhaustive = operation;
|
|
1550
|
+
throw new Error(`unknown supervisor pool operation ${String(exhaustive)}`);
|
|
1551
|
+
}
|
|
1274
1552
|
}
|
|
1275
|
-
this.completeShim(act, term, value);
|
|
1276
1553
|
}
|
|
1277
1554
|
structuralFormat(act, term) {
|
|
1278
1555
|
if (term.structural == null)
|
|
@@ -1282,7 +1559,7 @@ class ExecutorV1 {
|
|
|
1282
1559
|
const continueRendering = () => {
|
|
1283
1560
|
const next = renderer.next();
|
|
1284
1561
|
if (typeof next === "string") {
|
|
1285
|
-
this.completeShim(act, term,
|
|
1562
|
+
this.completeShim(act, term, stringValue(next));
|
|
1286
1563
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1287
1564
|
return;
|
|
1288
1565
|
}
|
|
@@ -1307,7 +1584,92 @@ class ExecutorV1 {
|
|
|
1307
1584
|
}
|
|
1308
1585
|
this.takeEdge(act, term.normal);
|
|
1309
1586
|
}
|
|
1587
|
+
sweepRcOwner(owner, actor, done) {
|
|
1588
|
+
let firstFault = null;
|
|
1589
|
+
let count = 0;
|
|
1590
|
+
const byType = new Map();
|
|
1591
|
+
const schedule = (work) => this.scheduler.enqueue(actor?.id ?? "actor:root", work);
|
|
1592
|
+
const next = () => {
|
|
1593
|
+
const blocks = owner.blocks;
|
|
1594
|
+
owner.blocks = new Set();
|
|
1595
|
+
if (!blocks.size) {
|
|
1596
|
+
owner.closed = true;
|
|
1597
|
+
if (actor && count)
|
|
1598
|
+
this.trace.snapshot("actor.rc_sweep", {
|
|
1599
|
+
actor_id: actor.id,
|
|
1600
|
+
rc_sweep: {
|
|
1601
|
+
count: String(count),
|
|
1602
|
+
by_type: [...byType]
|
|
1603
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1604
|
+
.map(([type_name, amount]) => ({
|
|
1605
|
+
type_name,
|
|
1606
|
+
count: String(amount),
|
|
1607
|
+
})),
|
|
1608
|
+
},
|
|
1609
|
+
});
|
|
1610
|
+
done(firstFault);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
const marking = blocks.values();
|
|
1614
|
+
const payloads = blocks.values();
|
|
1615
|
+
let remaining = 64;
|
|
1616
|
+
const release = () => {
|
|
1617
|
+
if (!remaining--) {
|
|
1618
|
+
remaining = 64;
|
|
1619
|
+
schedule(release);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const item = payloads.next();
|
|
1623
|
+
if (item.done) {
|
|
1624
|
+
schedule(next);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const cell = item.value;
|
|
1628
|
+
const payload = cell.value;
|
|
1629
|
+
cell.value = UNIT;
|
|
1630
|
+
blocks.delete(cell);
|
|
1631
|
+
this.closeValueAsync(payload, this.faultText(firstFault), (fault) => {
|
|
1632
|
+
firstFault = combineFaults(firstFault, fault);
|
|
1633
|
+
release();
|
|
1634
|
+
}, actor, owner);
|
|
1635
|
+
};
|
|
1636
|
+
const mark = () => {
|
|
1637
|
+
for (let i = 0; i < 64; i++) {
|
|
1638
|
+
const item = marking.next();
|
|
1639
|
+
if (item.done) {
|
|
1640
|
+
release();
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const cell = item.value;
|
|
1644
|
+
cell.dying = true;
|
|
1645
|
+
cell.refs = 0;
|
|
1646
|
+
cell.owner = null;
|
|
1647
|
+
count += 1;
|
|
1648
|
+
byType.set(cell.typeName, (byType.get(cell.typeName) ?? 0) + 1);
|
|
1649
|
+
}
|
|
1650
|
+
schedule(mark);
|
|
1651
|
+
};
|
|
1652
|
+
mark();
|
|
1653
|
+
};
|
|
1654
|
+
next();
|
|
1655
|
+
}
|
|
1656
|
+
finishConstruction(act, done) {
|
|
1657
|
+
if (!act.fn.actor_construction || !act.rcOwner)
|
|
1658
|
+
return false;
|
|
1659
|
+
const owner = act.rcOwner;
|
|
1660
|
+
act.rcOwner = null;
|
|
1661
|
+
this.running = false;
|
|
1662
|
+
this.sweepRcOwner(owner, act.context.actor, (fault) => {
|
|
1663
|
+
act.fault = combineFaults(act.fault, fault);
|
|
1664
|
+
done();
|
|
1665
|
+
const current = this.current;
|
|
1666
|
+
this.scheduler.enqueue(current.context.id, () => this.runFrame(current));
|
|
1667
|
+
});
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1310
1670
|
returnFrom(act, value) {
|
|
1671
|
+
if (this.finishConstruction(act, () => this.returnFrom(act, value)))
|
|
1672
|
+
return;
|
|
1311
1673
|
const caller = act.caller;
|
|
1312
1674
|
if (!caller) {
|
|
1313
1675
|
this.running = false;
|
|
@@ -1332,6 +1694,14 @@ class ExecutorV1 {
|
|
|
1332
1694
|
this.current = caller;
|
|
1333
1695
|
this.takeEdge(caller, act.normal);
|
|
1334
1696
|
}
|
|
1697
|
+
dynamicEntry(receiver, slot) {
|
|
1698
|
+
const table = this.pkg.vtables[receiver.vtable] ??
|
|
1699
|
+
this.pkg.vtables.find((table) => table.id === receiver.vtable);
|
|
1700
|
+
const entry = table?.slots.find((entry) => entry.slot === slot);
|
|
1701
|
+
if (!entry)
|
|
1702
|
+
throw new Error(`vtable ${receiver.vtable} has no slot ${slot}`);
|
|
1703
|
+
return entry;
|
|
1704
|
+
}
|
|
1335
1705
|
/// The wrapped value on its way to a trait method's `self`. A `copy` receiver
|
|
1336
1706
|
/// deep-clones, a `move` invalidates the trait object it came out of, and a
|
|
1337
1707
|
/// borrow passes the live value.
|
|
@@ -1359,6 +1729,8 @@ class ExecutorV1 {
|
|
|
1359
1729
|
this.takeEdge(act, unwind);
|
|
1360
1730
|
}
|
|
1361
1731
|
resumeUnwind(act, handback) {
|
|
1732
|
+
if (this.finishConstruction(act, () => this.resumeUnwind(act, handback)))
|
|
1733
|
+
return;
|
|
1362
1734
|
const fault = act.fault;
|
|
1363
1735
|
if (!fault) {
|
|
1364
1736
|
throw new Error(`${act.fn.name}: resume_unwind with no pending fault`);
|
|
@@ -1366,7 +1738,7 @@ class ExecutorV1 {
|
|
|
1366
1738
|
const caller = act.caller;
|
|
1367
1739
|
if (!caller) {
|
|
1368
1740
|
this.running = false;
|
|
1369
|
-
act.context.failed(fault);
|
|
1741
|
+
act.context.failed(fault, handback);
|
|
1370
1742
|
return;
|
|
1371
1743
|
}
|
|
1372
1744
|
if (!act.unwind) {
|
|
@@ -1387,7 +1759,11 @@ class ExecutorV1 {
|
|
|
1387
1759
|
this.current = caller;
|
|
1388
1760
|
this.takeEdge(caller, act.unwind);
|
|
1389
1761
|
}
|
|
1390
|
-
suspend(act,
|
|
1762
|
+
suspend(act, input) {
|
|
1763
|
+
const registration = resolveSuspension(input);
|
|
1764
|
+
if (registration.kind === "refused")
|
|
1765
|
+
throw new Error(`suspension ${registration.capability} was refused after admission`);
|
|
1766
|
+
const term = registration.term;
|
|
1391
1767
|
switch (term.kind) {
|
|
1392
1768
|
case "Yield": {
|
|
1393
1769
|
const generator = act.context.generator;
|
|
@@ -1436,37 +1812,27 @@ class ExecutorV1 {
|
|
|
1436
1812
|
return;
|
|
1437
1813
|
}
|
|
1438
1814
|
case "StreamSend": {
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
let closing = false;
|
|
1443
|
-
let cancelled = null;
|
|
1444
|
-
const wake = this.park(act, term, () => cancelWait());
|
|
1815
|
+
const sink = this.boundary(act, term.inputs[0]);
|
|
1816
|
+
const value = this.boundary(act, term.inputs[1]);
|
|
1817
|
+
const wake = this.park(act, term);
|
|
1445
1818
|
const cancel = act.context.cancel;
|
|
1819
|
+
const stop = this.sendStream(sink, value, term.detail.park, act.context.actor, (status, fault) => {
|
|
1820
|
+
wake(UNIT, fault, status);
|
|
1821
|
+
}, act.rcOwner);
|
|
1446
1822
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1447
|
-
|
|
1448
|
-
cancelWait();
|
|
1449
|
-
if (owned) {
|
|
1450
|
-
owned = false;
|
|
1451
|
-
closing = true;
|
|
1452
|
-
this.closeValueAsync(value, this.faultText(fault), (closeFault) => cancel(closeFault ?? fault));
|
|
1453
|
-
}
|
|
1454
|
-
else if (!closing)
|
|
1455
|
-
cancel(fault);
|
|
1823
|
+
stop(fault, (closeFault) => cancel?.(closeFault ?? fault));
|
|
1456
1824
|
};
|
|
1457
|
-
cancelWait = this.pipes.send(sink, value, term.detail.park, (status) => {
|
|
1458
|
-
owned = false;
|
|
1459
|
-
if (status === 0)
|
|
1460
|
-
wake(UNIT);
|
|
1461
|
-
else {
|
|
1462
|
-
closing = true;
|
|
1463
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => wake(UNIT, fault ?? cancelled, status));
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
1825
|
return;
|
|
1467
1826
|
}
|
|
1468
1827
|
case "StreamNext": {
|
|
1469
1828
|
const stream = this.boundary(act, term.inputs[0]);
|
|
1829
|
+
const adapter = stream.kind === "stream"
|
|
1830
|
+
? this.generatorStreams.get(stream.channelId)
|
|
1831
|
+
: undefined;
|
|
1832
|
+
if (adapter) {
|
|
1833
|
+
this.receiveGeneratorStream(act, term, adapter);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1470
1836
|
let cancelWait = () => { };
|
|
1471
1837
|
const wake = this.park(act, term, () => cancelWait());
|
|
1472
1838
|
cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault) => {
|
|
@@ -1479,10 +1845,13 @@ class ExecutorV1 {
|
|
|
1479
1845
|
}
|
|
1480
1846
|
case "Await": {
|
|
1481
1847
|
const task = this.taskFor(this.boundary(act, term.inputs[0]));
|
|
1482
|
-
|
|
1848
|
+
let unwatch = () => { };
|
|
1849
|
+
const wake = this.park(act, term, () => unwatch());
|
|
1483
1850
|
const ready = () => {
|
|
1484
|
-
if (!task.done)
|
|
1851
|
+
if (!task.done) {
|
|
1852
|
+
this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
|
|
1485
1853
|
return;
|
|
1854
|
+
}
|
|
1486
1855
|
if (task.fault)
|
|
1487
1856
|
wake(UNIT, task.fault);
|
|
1488
1857
|
else {
|
|
@@ -1492,6 +1861,11 @@ class ExecutorV1 {
|
|
|
1492
1861
|
}
|
|
1493
1862
|
};
|
|
1494
1863
|
task.changed.push(ready);
|
|
1864
|
+
unwatch = () => {
|
|
1865
|
+
const index = task.changed.indexOf(ready);
|
|
1866
|
+
if (index >= 0)
|
|
1867
|
+
task.changed.splice(index, 1);
|
|
1868
|
+
};
|
|
1495
1869
|
ready();
|
|
1496
1870
|
return;
|
|
1497
1871
|
}
|
|
@@ -1554,6 +1928,11 @@ class ExecutorV1 {
|
|
|
1554
1928
|
cancelTimer?.();
|
|
1555
1929
|
wake({ kind: "i64", value: BigInt(ready[0].index) });
|
|
1556
1930
|
}
|
|
1931
|
+
else {
|
|
1932
|
+
this.pendingActorWait(act, sources.map((value) => value.kind === "stream"
|
|
1933
|
+
? undefined
|
|
1934
|
+
: this.taskFor(value).actorTarget), hasTimeout, "select", (fault) => wake(UNIT, fault));
|
|
1935
|
+
}
|
|
1557
1936
|
};
|
|
1558
1937
|
if (hasTimeout) {
|
|
1559
1938
|
const duration = values[values.length - 1];
|
|
@@ -1581,27 +1960,115 @@ class ExecutorV1 {
|
|
|
1581
1960
|
const protocol = term.detail;
|
|
1582
1961
|
const inputs = term.inputs.map((input) => this.boundary(act, input));
|
|
1583
1962
|
const wake = this.park(act, term);
|
|
1963
|
+
let target;
|
|
1964
|
+
let checkWait;
|
|
1584
1965
|
const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault) => {
|
|
1585
1966
|
act.fault ??= closeFault ?? null;
|
|
1586
1967
|
return wake(this.completion(term.result_shape, term.error_shape, value, error));
|
|
1587
|
-
}, term.request_shapes)
|
|
1968
|
+
}, term.request_shapes, (actor) => {
|
|
1969
|
+
target = actor;
|
|
1970
|
+
checkWait?.();
|
|
1971
|
+
});
|
|
1588
1972
|
const cancel = act.context.cancel;
|
|
1589
1973
|
act.context.cancel = (fault) => cancelRequest((closeFault) => cancel(closeFault ?? fault));
|
|
1974
|
+
checkWait = () => this.pendingActorWait(act, [target], false, "ask", (fault) => cancelRequest((closeFault) => wake(UNIT, closeFault ?? fault)));
|
|
1975
|
+
checkWait();
|
|
1590
1976
|
return;
|
|
1591
1977
|
}
|
|
1592
1978
|
case "NativeIo": {
|
|
1979
|
+
if (term.detail.operation === "FileRead" ||
|
|
1980
|
+
term.detail.operation === "FileWrite") {
|
|
1981
|
+
const args = term.inputs.map((operand) => this.boundary(act, operand));
|
|
1982
|
+
const wake = this.park(act, term);
|
|
1983
|
+
const cancel = act.context.cancel;
|
|
1984
|
+
let stopped = act.context.pendingCancellation ?? this.scopeDeadline(act) ?? null;
|
|
1985
|
+
act.context.cancel = (fault = {
|
|
1986
|
+
kind: "panic",
|
|
1987
|
+
message: "task cancelled",
|
|
1988
|
+
cancelled: true,
|
|
1989
|
+
}) => {
|
|
1990
|
+
stopped ??= fault;
|
|
1991
|
+
};
|
|
1992
|
+
this.scheduler.submitHost(act.context.id, this.directories.io(term.detail.operation, args, () => stopped !== null || this.terminating), (outcome) => {
|
|
1993
|
+
// The producer is quiescent. Restore park's latch so cancellation
|
|
1994
|
+
// between readiness and frame resumption still retires the result.
|
|
1995
|
+
act.context.cancel = cancel;
|
|
1996
|
+
act.context.fileError = outcome.error;
|
|
1997
|
+
if (stopped) {
|
|
1998
|
+
this.closeValue(outcome.value, null);
|
|
1999
|
+
cancel(stopped);
|
|
2000
|
+
}
|
|
2001
|
+
else
|
|
2002
|
+
wake(outcome.value);
|
|
2003
|
+
}, (error) => {
|
|
2004
|
+
throw error;
|
|
2005
|
+
});
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
1593
2008
|
if (term.detail?.operation === "SinkFinish") {
|
|
1594
|
-
this.
|
|
1595
|
-
|
|
2009
|
+
const wake = this.park(act, term);
|
|
2010
|
+
const cancel = act.context.cancel;
|
|
2011
|
+
let stopped = null;
|
|
2012
|
+
act.context.cancel = (fault = {
|
|
2013
|
+
kind: "panic",
|
|
2014
|
+
message: "task cancelled",
|
|
2015
|
+
cancelled: true,
|
|
2016
|
+
}) => {
|
|
2017
|
+
stopped ??= fault;
|
|
2018
|
+
};
|
|
2019
|
+
this.finishSink(this.boundary(act, term.inputs[0]), null, () => {
|
|
2020
|
+
if (stopped)
|
|
2021
|
+
cancel(stopped);
|
|
2022
|
+
else
|
|
2023
|
+
wake(UNIT);
|
|
2024
|
+
});
|
|
1596
2025
|
return;
|
|
1597
2026
|
}
|
|
1598
|
-
|
|
2027
|
+
if (term.detail.operation !== "StdinReadLine") {
|
|
2028
|
+
const exhaustive = term.detail.operation;
|
|
2029
|
+
throw new Error(`unknown native I/O handler ${String(exhaustive)}`);
|
|
2030
|
+
}
|
|
2031
|
+
// The record is the raw line,
|
|
1599
2032
|
// so the records concatenate to the stdin the program consumed.
|
|
1600
2033
|
const line = this.stdin.readLine() ?? new Uint8Array();
|
|
1601
2034
|
this.trace.recordReplayInput({ kind: "stdin", data: new TextDecoder().decode(line) }, false);
|
|
1602
2035
|
this.park(act, term)(bytesValue([...line]));
|
|
1603
2036
|
return;
|
|
1604
2037
|
}
|
|
2038
|
+
case "Safepoint": {
|
|
2039
|
+
if (act.context.pollSuppression) {
|
|
2040
|
+
this.takeEdge(act, term.resumes[0]);
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
if (act.context.pendingCancellation && term.cancel) {
|
|
2044
|
+
act.fault = act.context.pendingCancellation;
|
|
2045
|
+
this.takeEdge(act, term.cancel);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const budget = term.detail?.budget;
|
|
2049
|
+
if (!Number.isInteger(budget) || budget <= 0)
|
|
2050
|
+
throw new Error("safepoint requires a positive compiler-authored budget");
|
|
2051
|
+
const countdown = act.context.pollBudget;
|
|
2052
|
+
countdown.remaining = (countdown.remaining || budget) - 1;
|
|
2053
|
+
if (countdown.remaining > 0) {
|
|
2054
|
+
this.takeEdge(act, term.resumes[0]);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
countdown.remaining = budget;
|
|
2058
|
+
this.scheduler.advanceSlice();
|
|
2059
|
+
const fault = act.context.pendingCancellation ?? this.scopeDeadline(act);
|
|
2060
|
+
if (fault && term.cancel) {
|
|
2061
|
+
act.fault = fault;
|
|
2062
|
+
this.takeEdge(act, term.cancel);
|
|
2063
|
+
}
|
|
2064
|
+
else if (this.scheduler.hasRunnableWork()) {
|
|
2065
|
+
this.park(act, term)(UNIT);
|
|
2066
|
+
}
|
|
2067
|
+
else {
|
|
2068
|
+
this.takeEdge(act, term.resumes[0]);
|
|
2069
|
+
}
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
1605
2072
|
case "Sleep": {
|
|
1606
2073
|
const duration = term.inputs[0]
|
|
1607
2074
|
? this.boundary(act, term.inputs[0])
|
|
@@ -1612,17 +2079,40 @@ class ExecutorV1 {
|
|
|
1612
2079
|
cancelTimer = this.scheduler.after(nanos, () => wake(UNIT));
|
|
1613
2080
|
return;
|
|
1614
2081
|
}
|
|
1615
|
-
default:
|
|
1616
|
-
|
|
2082
|
+
default: {
|
|
2083
|
+
const exhaustive = term;
|
|
2084
|
+
throw new Error(`unknown suspension handler ${String(exhaustive)}`);
|
|
2085
|
+
}
|
|
1617
2086
|
}
|
|
1618
2087
|
}
|
|
2088
|
+
pendingActorWait(act, targets, externalProgress, operation, fail) {
|
|
2089
|
+
const owner = act.context.actor;
|
|
2090
|
+
// Independent tasks carry actor ownership for cleanup, but do not hold its turn.
|
|
2091
|
+
if (!owner || owner.active !== act.context || !act.context.parked)
|
|
2092
|
+
return;
|
|
2093
|
+
this.actorWaits.ready(owner);
|
|
2094
|
+
if (externalProgress ||
|
|
2095
|
+
this.requestDeadline(act) !== undefined ||
|
|
2096
|
+
targets.some((target) => !target))
|
|
2097
|
+
return;
|
|
2098
|
+
const path = this.actorWaits.pending(owner, targets);
|
|
2099
|
+
if (path)
|
|
2100
|
+
fail({
|
|
2101
|
+
kind: "panic",
|
|
2102
|
+
message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
1619
2105
|
park(act, term, cleanup = () => { }) {
|
|
1620
2106
|
this.running = false;
|
|
2107
|
+
act.context.parked = true;
|
|
1621
2108
|
let pending = null;
|
|
1622
2109
|
const resume = (value, fault = null, edge = 0) => {
|
|
1623
2110
|
if (pending)
|
|
1624
2111
|
return false;
|
|
1625
2112
|
pending = { value, fault, edge };
|
|
2113
|
+
act.context.parked = false;
|
|
2114
|
+
if (act.context.actor?.active === act.context)
|
|
2115
|
+
this.actorWaits.ready(act.context.actor);
|
|
1626
2116
|
cleanup();
|
|
1627
2117
|
this.scheduler.enqueue(act.context.id, () => {
|
|
1628
2118
|
const outcome = pending;
|
|
@@ -1631,7 +2121,8 @@ class ExecutorV1 {
|
|
|
1631
2121
|
const fault = closeFault ?? (outcome.fault ? (act.fault ?? outcome.fault) : null);
|
|
1632
2122
|
if (fault) {
|
|
1633
2123
|
act.fault = fault;
|
|
1634
|
-
this.takeEdge(act,
|
|
2124
|
+
this.takeEdge(act, term.kind === "Safepoint" ||
|
|
2125
|
+
(fault.kind === "panic" && fault.cancelled)
|
|
1635
2126
|
? term.cancel
|
|
1636
2127
|
: term.unwind);
|
|
1637
2128
|
}
|
|
@@ -1649,13 +2140,15 @@ class ExecutorV1 {
|
|
|
1649
2140
|
// the cancellation latch live through that window and retire a result
|
|
1650
2141
|
// whose normal edge the consumer never entered.
|
|
1651
2142
|
if (outcome.fault)
|
|
1652
|
-
this.closeValueAsync(outcome.value, this.faultText(act.fault ?? outcome.fault), finish, act.context.actor);
|
|
2143
|
+
this.closeValueAsync(outcome.value, this.faultText(act.fault ?? outcome.fault), finish, act.context.actor, act.rcOwner);
|
|
1653
2144
|
else
|
|
1654
2145
|
finish();
|
|
1655
2146
|
});
|
|
1656
2147
|
return true;
|
|
1657
2148
|
};
|
|
1658
2149
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
2150
|
+
if (term.cancel == null)
|
|
2151
|
+
return;
|
|
1659
2152
|
if (pending)
|
|
1660
2153
|
pending.fault ??= fault;
|
|
1661
2154
|
else
|
|
@@ -1663,32 +2156,248 @@ class ExecutorV1 {
|
|
|
1663
2156
|
};
|
|
1664
2157
|
return resume;
|
|
1665
2158
|
}
|
|
1666
|
-
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
faultText(fault) {
|
|
1672
|
-
return !fault ||
|
|
1673
|
-
(fault.kind === "panic" && (fault.cancelled || fault.deadline))
|
|
1674
|
-
? null
|
|
1675
|
-
: fault.kind === "panic"
|
|
1676
|
-
? fault.message
|
|
1677
|
-
: (fault.message ?? trapMessage(fault.trap));
|
|
2159
|
+
sinkClosed(value) {
|
|
2160
|
+
const endpoint = value.kind === "sink"
|
|
2161
|
+
? this.senderEndpoints.get(value.channelId)
|
|
2162
|
+
: undefined;
|
|
2163
|
+
return endpoint ? !endpoint.open : this.pipes.isClosed(value);
|
|
1678
2164
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
2165
|
+
senderEndpoint(owner, open = true) {
|
|
2166
|
+
const id = this.ids.channel();
|
|
2167
|
+
owner.references++;
|
|
2168
|
+
if (open)
|
|
2169
|
+
owner.endpoints++;
|
|
2170
|
+
this.senderEndpoints.set(id, { owner, open });
|
|
2171
|
+
return { kind: "sink", channelId: id };
|
|
1681
2172
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
2173
|
+
finishSink(value, fault, done) {
|
|
2174
|
+
const endpoint = value.kind === "sink"
|
|
2175
|
+
? this.senderEndpoints.get(value.channelId)
|
|
2176
|
+
: undefined;
|
|
2177
|
+
if (!endpoint) {
|
|
2178
|
+
this.pipes.close(value, fault);
|
|
2179
|
+
done();
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
if (!endpoint.open) {
|
|
2183
|
+
done();
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
endpoint.open = false;
|
|
2187
|
+
endpoint.owner.endpoints--;
|
|
2188
|
+
if (fault || endpoint.owner.endpoints === 0) {
|
|
2189
|
+
const finish = () => {
|
|
2190
|
+
if (endpoint.owner.busy) {
|
|
2191
|
+
endpoint.owner.waiting.add(finish);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
this.finishSink(endpoint.owner.sink, fault, done);
|
|
2195
|
+
};
|
|
2196
|
+
finish();
|
|
2197
|
+
}
|
|
2198
|
+
else
|
|
2199
|
+
done();
|
|
2200
|
+
}
|
|
2201
|
+
sendStream(sink, value, park, actor, complete, rcOwner) {
|
|
2202
|
+
const endpoint = sink.kind === "sink"
|
|
2203
|
+
? this.senderEndpoints.get(sink.channelId)
|
|
2204
|
+
: undefined;
|
|
2205
|
+
let owned = true;
|
|
2206
|
+
let finished = false;
|
|
2207
|
+
let closing = false;
|
|
2208
|
+
let claimed = false;
|
|
2209
|
+
let stopped = null;
|
|
2210
|
+
let encoder = null;
|
|
2211
|
+
let child = null;
|
|
2212
|
+
let withdraw = () => { };
|
|
2213
|
+
const drained = [];
|
|
2214
|
+
const release = () => {
|
|
2215
|
+
if (!claimed || !endpoint)
|
|
2216
|
+
return;
|
|
2217
|
+
claimed = false;
|
|
2218
|
+
endpoint.owner.busy = false;
|
|
2219
|
+
for (const wake of endpoint.owner.waiting)
|
|
2220
|
+
this.scheduler.enqueue(actor?.id ?? "actor:root", wake);
|
|
2221
|
+
endpoint.owner.waiting.clear();
|
|
2222
|
+
};
|
|
2223
|
+
const finish = (status, fault = null) => {
|
|
2224
|
+
if (finished || closing)
|
|
2225
|
+
return;
|
|
2226
|
+
closing = true;
|
|
2227
|
+
release();
|
|
2228
|
+
const done = (closeFault = null) => {
|
|
2229
|
+
finished = true;
|
|
2230
|
+
const failure = combineFaults(fault, closeFault);
|
|
2231
|
+
if (stopped)
|
|
2232
|
+
for (const wake of drained.splice(0))
|
|
2233
|
+
wake(failure);
|
|
2234
|
+
else
|
|
2235
|
+
complete(status, failure);
|
|
2236
|
+
};
|
|
2237
|
+
if (owned) {
|
|
2238
|
+
owned = false;
|
|
2239
|
+
this.closeValueAsync(value, this.faultText(fault ?? stopped), done, actor, rcOwner);
|
|
2240
|
+
}
|
|
2241
|
+
else
|
|
2242
|
+
done();
|
|
2243
|
+
};
|
|
2244
|
+
const start = () => {
|
|
2245
|
+
if (stopped || finished || closing)
|
|
2246
|
+
return;
|
|
2247
|
+
if (!endpoint) {
|
|
2248
|
+
withdraw = this.pipes.send(sink, value, park, (status) => {
|
|
2249
|
+
if (status === 0)
|
|
2250
|
+
owned = false;
|
|
2251
|
+
finish(status);
|
|
2252
|
+
});
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
if (!endpoint.open) {
|
|
2256
|
+
finish(1);
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const owner = endpoint.owner;
|
|
2260
|
+
if (owner.busy) {
|
|
2261
|
+
if (!park) {
|
|
2262
|
+
finish(2);
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
owner.waiting.add(start);
|
|
2266
|
+
withdraw = () => {
|
|
2267
|
+
owner.waiting.delete(start);
|
|
2268
|
+
};
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
owner.busy = true;
|
|
2272
|
+
claimed = true;
|
|
2273
|
+
const callable = owner.encoder;
|
|
2274
|
+
encoder = this.invokeFrame(actor, calleeFunction(callable, null), callable.kind === "closure" ? [callable.environment, value] : [value], (encoded) => {
|
|
2275
|
+
encoder = null;
|
|
2276
|
+
if (stopped) {
|
|
2277
|
+
this.closeValueAsync(encoded, this.faultText(stopped), (fault) => finish(0, fault), actor, rcOwner);
|
|
2278
|
+
}
|
|
2279
|
+
else {
|
|
2280
|
+
child = this.sendStream(owner.sink, encoded, park, actor, (status, fault) => {
|
|
2281
|
+
child = null;
|
|
2282
|
+
finish(status, fault);
|
|
2283
|
+
}, rcOwner);
|
|
2284
|
+
}
|
|
2285
|
+
}, (fault) => {
|
|
2286
|
+
encoder = null;
|
|
2287
|
+
finish(3, fault);
|
|
2288
|
+
}, rcOwner);
|
|
2289
|
+
};
|
|
2290
|
+
start();
|
|
2291
|
+
return (fault, done) => {
|
|
2292
|
+
if (finished) {
|
|
2293
|
+
done(null);
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
stopped ??= fault;
|
|
2297
|
+
drained.push(done);
|
|
2298
|
+
withdraw();
|
|
2299
|
+
if (closing)
|
|
2300
|
+
return;
|
|
2301
|
+
if (encoder)
|
|
2302
|
+
encoder.cancel?.(fault);
|
|
2303
|
+
else if (child)
|
|
2304
|
+
child(fault, (failure) => {
|
|
2305
|
+
child = null;
|
|
2306
|
+
finish(0, failure);
|
|
2307
|
+
});
|
|
2308
|
+
else
|
|
2309
|
+
finish(0);
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
receiveGeneratorStream(act, term, adapter) {
|
|
2313
|
+
const wake = this.park(act, term);
|
|
2314
|
+
const receive = (value, fault) => {
|
|
2315
|
+
if (fault)
|
|
2316
|
+
wake(UNIT, fault);
|
|
2317
|
+
else
|
|
2318
|
+
wake(this.variant(term.result_shape, value === null ? "OptionNone" : "OptionSome", value === null ? [] : [value]));
|
|
2319
|
+
};
|
|
2320
|
+
if (adapter.pending !== null) {
|
|
2321
|
+
const value = adapter.pending;
|
|
2322
|
+
adapter.pending = null;
|
|
2323
|
+
receive(value, null);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
const generator = adapter.generator;
|
|
2327
|
+
if (generator.done) {
|
|
2328
|
+
receive(null, adapter.fault ?? generator.fault);
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
if (term.detail.park)
|
|
2332
|
+
adapter.waiting = receive;
|
|
2333
|
+
else
|
|
2334
|
+
receive(null, null);
|
|
2335
|
+
const cancel = act.context.cancel;
|
|
2336
|
+
if (term.detail.park)
|
|
2337
|
+
act.context.cancel = (fault) => {
|
|
2338
|
+
adapter.waiting = undefined;
|
|
2339
|
+
this.closeGenerator(generator, (closeFault) => cancel?.(closeFault ?? fault));
|
|
2340
|
+
};
|
|
2341
|
+
if (adapter.receiving)
|
|
2342
|
+
return;
|
|
2343
|
+
adapter.receiving = true;
|
|
2344
|
+
generator.receive = (value, fault) => {
|
|
2345
|
+
adapter.receiving = false;
|
|
2346
|
+
if (adapter.closed) {
|
|
2347
|
+
if (value !== null)
|
|
2348
|
+
this.closeValueAsync(value, null, () => { });
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
adapter.fault = fault;
|
|
2352
|
+
const waiting = adapter.waiting;
|
|
2353
|
+
adapter.waiting = undefined;
|
|
2354
|
+
if (waiting)
|
|
2355
|
+
waiting(value, fault);
|
|
2356
|
+
else
|
|
2357
|
+
adapter.pending = value;
|
|
2358
|
+
};
|
|
2359
|
+
if (generator.started) {
|
|
2360
|
+
const resume = generator.resume;
|
|
2361
|
+
generator.resume = undefined;
|
|
2362
|
+
if (!resume)
|
|
2363
|
+
throw new Error("generator stream lost its suspended yield");
|
|
2364
|
+
resume();
|
|
2365
|
+
}
|
|
2366
|
+
else {
|
|
2367
|
+
this.scheduler.enqueue(generator.id, () => {
|
|
2368
|
+
if (generator.done)
|
|
2369
|
+
return;
|
|
2370
|
+
generator.started = true;
|
|
2371
|
+
this.runFrame(generator.frame);
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
newPipe(capacity) {
|
|
2376
|
+
const id = this.ids.channel();
|
|
2377
|
+
this.trace.allocateId("channel", id, this.current?.context.id ?? "actor:root");
|
|
2378
|
+
return this.pipes.create(id, capacity);
|
|
2379
|
+
}
|
|
2380
|
+
faultText(fault) {
|
|
2381
|
+
return !fault ||
|
|
2382
|
+
(fault.kind === "panic" && (fault.cancelled || fault.deadline))
|
|
2383
|
+
? null
|
|
2384
|
+
: fault.kind === "panic"
|
|
2385
|
+
? fault.message
|
|
2386
|
+
: (fault.message ?? trapMessage(fault.trap));
|
|
2387
|
+
}
|
|
2388
|
+
pipeFault(act) {
|
|
2389
|
+
return this.faultText(act.context.actor?.crashing ?? act.fault);
|
|
2390
|
+
}
|
|
2391
|
+
releaseOperation(act, value, finish) {
|
|
2392
|
+
let synchronous = true;
|
|
2393
|
+
let completed = false;
|
|
2394
|
+
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
2395
|
+
completed = true;
|
|
2396
|
+
finish?.();
|
|
2397
|
+
act.fault = combineFaults(act.fault, fault);
|
|
2398
|
+
if (!synchronous)
|
|
1690
2399
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1691
|
-
});
|
|
2400
|
+
}, act.context.actor, act.rcOwner);
|
|
1692
2401
|
synchronous = false;
|
|
1693
2402
|
if (!completed)
|
|
1694
2403
|
this.running = false;
|
|
@@ -1713,7 +2422,7 @@ class ExecutorV1 {
|
|
|
1713
2422
|
receive?.(null, generator.fault);
|
|
1714
2423
|
for (const closed of generator.closed.splice(0))
|
|
1715
2424
|
closed(generator.fault);
|
|
1716
|
-
});
|
|
2425
|
+
}, generator.frame.context.actor, generator.frame.rcOwner);
|
|
1717
2426
|
}
|
|
1718
2427
|
closeGenerator(generator, done) {
|
|
1719
2428
|
if (generator.done) {
|
|
@@ -1726,7 +2435,7 @@ class ExecutorV1 {
|
|
|
1726
2435
|
else
|
|
1727
2436
|
generator.frame.context.cancel?.();
|
|
1728
2437
|
}
|
|
1729
|
-
closeValueAsync(value, fault, done, actor = this.current.context.actor) {
|
|
2438
|
+
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner) {
|
|
1730
2439
|
const pending = [value];
|
|
1731
2440
|
let firstFault = null;
|
|
1732
2441
|
const next = (failure = null) => {
|
|
@@ -1737,15 +2446,41 @@ class ExecutorV1 {
|
|
|
1737
2446
|
while (pending.length) {
|
|
1738
2447
|
const value = pending.pop();
|
|
1739
2448
|
switch (value.kind) {
|
|
2449
|
+
case "string":
|
|
2450
|
+
value.release();
|
|
2451
|
+
break;
|
|
1740
2452
|
case "rc":
|
|
1741
2453
|
if (!value.closed) {
|
|
1742
2454
|
value.closed = true;
|
|
1743
|
-
if (--value.cell.refs === 0) {
|
|
2455
|
+
if (value.cell && !value.cell.dying && --value.cell.refs === 0) {
|
|
2456
|
+
value.cell.owner?.blocks.delete(value.cell);
|
|
2457
|
+
value.cell.owner = null;
|
|
1744
2458
|
pending.push(value.cell.value);
|
|
1745
2459
|
value.cell.value = UNIT;
|
|
1746
2460
|
}
|
|
1747
2461
|
}
|
|
1748
2462
|
break;
|
|
2463
|
+
case "weak":
|
|
2464
|
+
if (!value.closed) {
|
|
2465
|
+
value.closed = true;
|
|
2466
|
+
value.cell.weak -= 1;
|
|
2467
|
+
}
|
|
2468
|
+
break;
|
|
2469
|
+
case "host_resource": {
|
|
2470
|
+
if (value.owner.closed)
|
|
2471
|
+
break;
|
|
2472
|
+
this.closeHostResource(value);
|
|
2473
|
+
break;
|
|
2474
|
+
}
|
|
2475
|
+
case "timer": {
|
|
2476
|
+
if (value.closed)
|
|
2477
|
+
break;
|
|
2478
|
+
const resource = this.pkg.resources?.[value.resource];
|
|
2479
|
+
if (resource?.kind !== "opaque")
|
|
2480
|
+
throw new Error("opaque owner lost its selected close");
|
|
2481
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
1749
2484
|
case "task": {
|
|
1750
2485
|
// Dropping a handle does not cancel scoped work. Join drains the
|
|
1751
2486
|
// task, and scope close releases any result that was never taken.
|
|
@@ -1753,7 +2488,7 @@ class ExecutorV1 {
|
|
|
1753
2488
|
if (task?.done) {
|
|
1754
2489
|
const result = task.value;
|
|
1755
2490
|
task.value = UNIT;
|
|
1756
|
-
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor);
|
|
2491
|
+
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner);
|
|
1757
2492
|
return;
|
|
1758
2493
|
}
|
|
1759
2494
|
if (task?.close) {
|
|
@@ -1777,11 +2512,11 @@ class ExecutorV1 {
|
|
|
1777
2512
|
break;
|
|
1778
2513
|
case "record": {
|
|
1779
2514
|
const resource = (this.pkg.resources ?? []).find((resource) => resource.kind === "record" && resource.shape === value.shape);
|
|
1780
|
-
if (resource?.
|
|
2515
|
+
if (resource?.kind === "record") {
|
|
1781
2516
|
if (this.consumedResources.has(value))
|
|
1782
2517
|
break;
|
|
1783
2518
|
if (!this.closingResources.has(value)) {
|
|
1784
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next);
|
|
2519
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
1785
2520
|
return;
|
|
1786
2521
|
}
|
|
1787
2522
|
this.consumedResources.add(value);
|
|
@@ -1795,17 +2530,58 @@ class ExecutorV1 {
|
|
|
1795
2530
|
case "vector":
|
|
1796
2531
|
pending.push(...[...value.items].reverse());
|
|
1797
2532
|
break;
|
|
1798
|
-
case "
|
|
1799
|
-
|
|
2533
|
+
case "stream": {
|
|
2534
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2535
|
+
if (adapter) {
|
|
2536
|
+
this.generatorStreams.delete(value.channelId);
|
|
2537
|
+
adapter.closed = true;
|
|
2538
|
+
if (adapter.pending !== null)
|
|
2539
|
+
pending.push(adapter.pending);
|
|
2540
|
+
adapter.pending = null;
|
|
2541
|
+
adapter.waiting = undefined;
|
|
2542
|
+
this.closeGenerator(adapter.generator, next);
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
1800
2545
|
pending.push(...this.pipes.close(value, currentFault).reverse());
|
|
1801
2546
|
break;
|
|
2547
|
+
}
|
|
2548
|
+
case "sink": {
|
|
2549
|
+
const endpoint = this.senderEndpoints.get(value.channelId);
|
|
2550
|
+
if (endpoint) {
|
|
2551
|
+
this.finishSink(value, currentFault, () => {
|
|
2552
|
+
this.senderEndpoints.delete(value.channelId);
|
|
2553
|
+
if (--endpoint.owner.references === 0)
|
|
2554
|
+
pending.push(endpoint.owner.sink, endpoint.owner.encoder);
|
|
2555
|
+
next();
|
|
2556
|
+
});
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
else
|
|
2560
|
+
pending.push(...this.pipes.close(value, currentFault).reverse());
|
|
2561
|
+
break;
|
|
2562
|
+
}
|
|
1802
2563
|
}
|
|
1803
2564
|
}
|
|
1804
2565
|
done(firstFault);
|
|
1805
2566
|
};
|
|
1806
2567
|
next();
|
|
1807
2568
|
}
|
|
2569
|
+
closeHostResource(value) {
|
|
2570
|
+
if (value.owner.closed)
|
|
2571
|
+
return;
|
|
2572
|
+
const resource = this.pkg.resources?.[value.resource];
|
|
2573
|
+
const release = resource?.kind === "nominal"
|
|
2574
|
+
? resolveExtern({ symbol: resource.release })
|
|
2575
|
+
: null;
|
|
2576
|
+
if (release?.kind !== "directory" || release.operation.action !== "close")
|
|
2577
|
+
throw new Error("filesystem owner lost its selected release");
|
|
2578
|
+
this.directories.call(release.operation, [value]);
|
|
2579
|
+
}
|
|
1808
2580
|
closeValue(value, fault) {
|
|
2581
|
+
if (value.kind === "host_resource")
|
|
2582
|
+
this.closeHostResource(value);
|
|
2583
|
+
if (value.kind === "string")
|
|
2584
|
+
value.release();
|
|
1809
2585
|
if (value.kind === "closure")
|
|
1810
2586
|
this.closeValue(value.environment, fault);
|
|
1811
2587
|
else if (value.kind === "sink" || value.kind === "stream")
|
|
@@ -1854,25 +2630,33 @@ class ExecutorV1 {
|
|
|
1854
2630
|
for (const changed of [...task.changed])
|
|
1855
2631
|
changed();
|
|
1856
2632
|
}
|
|
1857
|
-
invokeFrame(actor, callable, args, returned, failed) {
|
|
1858
|
-
const
|
|
2633
|
+
invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false) {
|
|
2634
|
+
const context = {
|
|
1859
2635
|
id: actor?.id ?? "actor:root",
|
|
1860
2636
|
actor,
|
|
2637
|
+
rcOwner,
|
|
1861
2638
|
returned,
|
|
1862
|
-
failed,
|
|
1863
|
-
}
|
|
2639
|
+
failed: (fault) => failed(fault),
|
|
2640
|
+
};
|
|
2641
|
+
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
1864
2642
|
let cancelled = null;
|
|
1865
|
-
|
|
2643
|
+
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1866
2644
|
cancelled = fault;
|
|
1867
2645
|
};
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2646
|
+
if (inline) {
|
|
2647
|
+
this.current = frame;
|
|
2648
|
+
this.running = true;
|
|
2649
|
+
}
|
|
2650
|
+
else {
|
|
2651
|
+
this.scheduler.enqueue(frame.context.id, () => {
|
|
2652
|
+
context.cancel = undefined;
|
|
2653
|
+
if (cancelled && frame.context === context)
|
|
2654
|
+
context.failed(cancelled);
|
|
2655
|
+
else
|
|
2656
|
+
this.runFrame(frame);
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
return context;
|
|
1876
2660
|
}
|
|
1877
2661
|
variant(shapeId, role, payload = []) {
|
|
1878
2662
|
const shape = shapeId == null ? undefined : this.pkg.variants[shapeId];
|
|
@@ -1920,12 +2704,21 @@ class ExecutorV1 {
|
|
|
1920
2704
|
? { kind: "Actor", identity: act.context.actor.identity }
|
|
1921
2705
|
: { kind: "Task" };
|
|
1922
2706
|
switch (operation.op) {
|
|
2707
|
+
case "identity": {
|
|
2708
|
+
const [left, right] = args;
|
|
2709
|
+
this.completeShim(act, term, {
|
|
2710
|
+
kind: "bool",
|
|
2711
|
+
value: actorIdentical(left, right),
|
|
2712
|
+
});
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
1923
2715
|
case "spawn": {
|
|
1924
2716
|
const layout = this.pkg.actors?.find((actor) => actor.id === operation.actor);
|
|
1925
2717
|
if (!layout)
|
|
1926
2718
|
throw new Error(`actor layout ${operation.actor} is missing`);
|
|
1927
2719
|
let supplied = 0;
|
|
1928
2720
|
const actor = {
|
|
2721
|
+
rcOwner: act.rcOwner ?? { blocks: new Set(), closed: false },
|
|
1929
2722
|
identity: ++this.actorIdentity,
|
|
1930
2723
|
id: this.ids.actor(),
|
|
1931
2724
|
layout,
|
|
@@ -1935,9 +2728,11 @@ class ExecutorV1 {
|
|
|
1935
2728
|
fields: layout.state_fields.map((field) => field.deferred ? UNIT : (args[supplied++] ?? UNIT)),
|
|
1936
2729
|
},
|
|
1937
2730
|
mailbox: [],
|
|
2731
|
+
timers: new Set(),
|
|
1938
2732
|
disposals: [],
|
|
1939
2733
|
admission: [],
|
|
1940
2734
|
busy: true,
|
|
2735
|
+
constructing: true,
|
|
1941
2736
|
alive: true,
|
|
1942
2737
|
completed: false,
|
|
1943
2738
|
closing: false,
|
|
@@ -1947,44 +2742,194 @@ class ExecutorV1 {
|
|
|
1947
2742
|
closingFault: null,
|
|
1948
2743
|
crashing: null,
|
|
1949
2744
|
active: null,
|
|
1950
|
-
|
|
2745
|
+
terminalHook: null,
|
|
1951
2746
|
closed: [],
|
|
1952
2747
|
};
|
|
1953
2748
|
this.actors.set(actor.id, actor);
|
|
1954
2749
|
this.trace.allocateId("actor", actor.id, act.context.id);
|
|
1955
2750
|
this.trace.snapshot("actor.spawn", { actor_id: actor.id });
|
|
1956
2751
|
const handle = { kind: "actor", id: actor.id };
|
|
1957
|
-
|
|
2752
|
+
act.context.cancel = (fault = {
|
|
2753
|
+
kind: "panic",
|
|
2754
|
+
message: "actor construction cancelled",
|
|
2755
|
+
cancelled: true,
|
|
2756
|
+
}) => {
|
|
2757
|
+
actor.closing = true;
|
|
2758
|
+
if (actor.active) {
|
|
2759
|
+
actor.active.pendingCancellation ??= fault;
|
|
2760
|
+
actor.active.cancel?.(actor.active.pendingCancellation);
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
1958
2763
|
const failed = (fault) => {
|
|
2764
|
+
act.context.cancel = undefined;
|
|
2765
|
+
actor.active = null;
|
|
1959
2766
|
actor.alive = false;
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2767
|
+
actor.constructing = false;
|
|
2768
|
+
act.rcOwner = null;
|
|
2769
|
+
this.cancelActorTimers(actor);
|
|
2770
|
+
this.drainMailbox(actor, this.faultText(fault), (payloadFault) => {
|
|
2771
|
+
const primary = combineFaults(fault, payloadFault);
|
|
2772
|
+
this.closeValueAsync(actor.state, this.faultText(primary), (stateFault) => {
|
|
2773
|
+
const retained = combineFaults(primary, stateFault);
|
|
2774
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
2775
|
+
actor.completed = true;
|
|
2776
|
+
for (const closed of actor.closed.splice(0))
|
|
2777
|
+
closed();
|
|
2778
|
+
this.raiseFault(act, combineFaults(retained, sweepFault), term.unwind);
|
|
2779
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2780
|
+
});
|
|
2781
|
+
}, actor, actor.rcOwner);
|
|
1968
2782
|
});
|
|
1969
2783
|
};
|
|
1970
2784
|
const started = () => {
|
|
2785
|
+
if (actor.closing) {
|
|
2786
|
+
failed(actor.active?.pendingCancellation ?? {
|
|
2787
|
+
kind: "panic",
|
|
2788
|
+
message: "actor construction cancelled",
|
|
2789
|
+
cancelled: true,
|
|
2790
|
+
});
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
actor.constructing = false;
|
|
2794
|
+
actor.active = null;
|
|
2795
|
+
act.context.cancel = undefined;
|
|
2796
|
+
act.rcOwner = null;
|
|
1971
2797
|
actor.busy = false;
|
|
2798
|
+
this.startPeriodicTimers(actor);
|
|
1972
2799
|
this.dispatchActor(actor);
|
|
1973
2800
|
this.completeShim(act, term, handle);
|
|
1974
|
-
this.
|
|
2801
|
+
this.current = act;
|
|
2802
|
+
this.running = true;
|
|
1975
2803
|
};
|
|
2804
|
+
// Construction enters the same interpreter loop as its caller. Only
|
|
2805
|
+
// a real suspension queues work and advances deterministic scheduling.
|
|
1976
2806
|
const start = () => {
|
|
1977
|
-
if (layout.start
|
|
1978
|
-
this.invokeFrame(actor, layout.start, [actor.state], started, failed);
|
|
2807
|
+
if (layout.start != null)
|
|
2808
|
+
actor.active = this.invokeFrame(actor, layout.start, [actor.state], started, failed, actor.rcOwner, true);
|
|
1979
2809
|
else
|
|
1980
2810
|
started();
|
|
1981
2811
|
};
|
|
1982
|
-
if (layout.init
|
|
1983
|
-
this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed);
|
|
2812
|
+
if (layout.init != null)
|
|
2813
|
+
actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
|
|
1984
2814
|
else
|
|
1985
2815
|
start();
|
|
1986
2816
|
return;
|
|
1987
2817
|
}
|
|
2818
|
+
case "timer_schedule": {
|
|
2819
|
+
const [target, after, overdue, hasOverdue, payload] = args;
|
|
2820
|
+
const actor = this.actorFor(target);
|
|
2821
|
+
if (actor !== act.context.actor ||
|
|
2822
|
+
after?.kind !== "i64" ||
|
|
2823
|
+
overdue?.kind !== "i64" ||
|
|
2824
|
+
hasOverdue?.kind !== "bool" ||
|
|
2825
|
+
payload?.kind !== "record")
|
|
2826
|
+
throw new Error("timer schedule lost its checked owner or timing");
|
|
2827
|
+
const handler = actor.layout.handlers.find((handler) => handler.message_id === operation.message);
|
|
2828
|
+
if (!handler)
|
|
2829
|
+
throw new Error("timer schedule lost its handler");
|
|
2830
|
+
if (this.rootComplete || actor.closing) {
|
|
2831
|
+
this.running = false;
|
|
2832
|
+
const refused = {
|
|
2833
|
+
kind: "panic",
|
|
2834
|
+
actorSendFailed: true,
|
|
2835
|
+
message: "actor timer admission is closed",
|
|
2836
|
+
};
|
|
2837
|
+
this.closeValueAsync(payload, faultReport(refused), (fault) => {
|
|
2838
|
+
this.raiseFault(act, combineFaults(refused, fault), term.unwind);
|
|
2839
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2840
|
+
}, actor, act.rcOwner);
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
let phase = "scheduled";
|
|
2844
|
+
let pending = payload;
|
|
2845
|
+
let overdueAt = null;
|
|
2846
|
+
let disarm = null;
|
|
2847
|
+
const forget = () => {
|
|
2848
|
+
disarm?.();
|
|
2849
|
+
disarm = null;
|
|
2850
|
+
actor.timers.delete(timer);
|
|
2851
|
+
};
|
|
2852
|
+
const miss = (discarded) => {
|
|
2853
|
+
if (phase !== "fired")
|
|
2854
|
+
return;
|
|
2855
|
+
phase = hasOverdue.value ? "overdue" : "cancelled";
|
|
2856
|
+
forget();
|
|
2857
|
+
if (discarded && !hasOverdue.value)
|
|
2858
|
+
this.trace.writeStderr("actor.timer_discarded: the one-shot handler did not start\n", null);
|
|
2859
|
+
if (!hasOverdue.value || !actor.alive)
|
|
2860
|
+
return;
|
|
2861
|
+
const fault = {
|
|
2862
|
+
kind: "panic",
|
|
2863
|
+
timerOverdue: true,
|
|
2864
|
+
message: "actor timer did not start before its overdue deadline",
|
|
2865
|
+
};
|
|
2866
|
+
if (actor.active) {
|
|
2867
|
+
actor.active.pendingCancellation ??= fault;
|
|
2868
|
+
actor.active.cancel?.(actor.active.pendingCancellation);
|
|
2869
|
+
}
|
|
2870
|
+
else
|
|
2871
|
+
this.crashActor(actor, fault, handler);
|
|
2872
|
+
};
|
|
2873
|
+
const timer = {
|
|
2874
|
+
cancel: () => {
|
|
2875
|
+
if (phase !== "overdue")
|
|
2876
|
+
phase = "cancelled";
|
|
2877
|
+
forget();
|
|
2878
|
+
if (pending) {
|
|
2879
|
+
actor.disposals.push(pending);
|
|
2880
|
+
pending = null;
|
|
2881
|
+
this.dispatchActor(actor);
|
|
2882
|
+
}
|
|
2883
|
+
},
|
|
2884
|
+
started: () => {
|
|
2885
|
+
if (phase === "fired" &&
|
|
2886
|
+
overdueAt !== null &&
|
|
2887
|
+
this.trace.virtualTimeMs >= overdueAt)
|
|
2888
|
+
miss(false);
|
|
2889
|
+
const allowed = phase !== "overdue";
|
|
2890
|
+
if (phase === "fired")
|
|
2891
|
+
phase = "started";
|
|
2892
|
+
forget();
|
|
2893
|
+
return allowed;
|
|
2894
|
+
},
|
|
2895
|
+
discarded: () => miss(true),
|
|
2896
|
+
};
|
|
2897
|
+
actor.timers.add(timer);
|
|
2898
|
+
disarm = this.scheduler.after(after.value, () => {
|
|
2899
|
+
disarm = null;
|
|
2900
|
+
if (phase !== "scheduled")
|
|
2901
|
+
return;
|
|
2902
|
+
phase = "fired";
|
|
2903
|
+
pending = null;
|
|
2904
|
+
if (hasOverdue.value) {
|
|
2905
|
+
overdueAt =
|
|
2906
|
+
this.trace.virtualTimeMs + Number(overdue.value) / 1_000_000;
|
|
2907
|
+
disarm = this.scheduler.after(overdue.value, () => miss(false));
|
|
2908
|
+
}
|
|
2909
|
+
const message = {
|
|
2910
|
+
timer,
|
|
2911
|
+
caller: { kind: "Timer" },
|
|
2912
|
+
handler,
|
|
2913
|
+
payload: payload.fields,
|
|
2914
|
+
reply: false,
|
|
2915
|
+
obligated: false,
|
|
2916
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
2917
|
+
};
|
|
2918
|
+
const status = this.admitActorMessage(actor, message);
|
|
2919
|
+
if (status === "full" || status === "closed") {
|
|
2920
|
+
actor.disposals.push(payload);
|
|
2921
|
+
timer.discarded();
|
|
2922
|
+
this.dispatchActor(actor);
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
this.completeShim(act, term, {
|
|
2926
|
+
kind: "timer",
|
|
2927
|
+
resource: operation.resource,
|
|
2928
|
+
cancel: () => timer.cancel(),
|
|
2929
|
+
closed: false,
|
|
2930
|
+
});
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
1988
2933
|
case "stream_start": {
|
|
1989
2934
|
const [target, payload] = args;
|
|
1990
2935
|
const fields = fieldsOf(payload);
|
|
@@ -2001,7 +2946,7 @@ class ExecutorV1 {
|
|
|
2001
2946
|
this.raiseFault(act, { kind: "panic", message: "stream producer is closed" }, term.unwind);
|
|
2002
2947
|
}
|
|
2003
2948
|
else {
|
|
2004
|
-
if (actor.layout.mailbox_capacity
|
|
2949
|
+
if (actor.layout.mailbox_capacity != null &&
|
|
2005
2950
|
actor.mailbox.length >= actor.layout.mailbox_capacity) {
|
|
2006
2951
|
actor.admission.push(admit);
|
|
2007
2952
|
return;
|
|
@@ -2035,6 +2980,10 @@ class ExecutorV1 {
|
|
|
2035
2980
|
task.actorError = error;
|
|
2036
2981
|
this.settleTask(task, value ?? UNIT, closeFault ?? null);
|
|
2037
2982
|
return true;
|
|
2983
|
+
}, undefined, (actor) => {
|
|
2984
|
+
task.actorTarget = actor;
|
|
2985
|
+
for (const changed of [...task.changed])
|
|
2986
|
+
changed();
|
|
2038
2987
|
});
|
|
2039
2988
|
task.close = (done) => cancelRequest((fault) => {
|
|
2040
2989
|
this.settleTask(task, UNIT, fault);
|
|
@@ -2071,15 +3020,17 @@ class ExecutorV1 {
|
|
|
2071
3020
|
this.variant(shapes.success, discarded ? "DeliveryDiscarded" : "DeliveryAccepted"),
|
|
2072
3021
|
]);
|
|
2073
3022
|
const rejected = (reason) => this.variant(term.result_shape, "ResultErr", [
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
3023
|
+
this.variant(term.error_shape, "ActorErrorRejected", [
|
|
3024
|
+
{
|
|
3025
|
+
kind: "record",
|
|
3026
|
+
typeId: this.aggregateName(shapes.failure),
|
|
3027
|
+
shape: shapes.failure,
|
|
3028
|
+
fields: [
|
|
3029
|
+
this.variant(shapes.reason, sendErrorRole(reason)),
|
|
3030
|
+
request,
|
|
3031
|
+
],
|
|
3032
|
+
},
|
|
3033
|
+
]),
|
|
2083
3034
|
]);
|
|
2084
3035
|
let parked = false;
|
|
2085
3036
|
const finish = (value) => {
|
|
@@ -2092,87 +3043,38 @@ class ExecutorV1 {
|
|
|
2092
3043
|
finish(rejected("Dead"));
|
|
2093
3044
|
return;
|
|
2094
3045
|
}
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
finish(report);
|
|
2103
|
-
};
|
|
2104
|
-
const discard = (value, report) => {
|
|
2105
|
-
let settled = false;
|
|
2106
|
-
let waiting = false;
|
|
2107
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
2108
|
-
act.fault ??= fault;
|
|
2109
|
-
settled = true;
|
|
2110
|
-
if (waiting)
|
|
2111
|
-
finish(report);
|
|
2112
|
-
else
|
|
2113
|
-
this.completeShim(act, term, report);
|
|
2114
|
-
}, act.context.actor);
|
|
2115
|
-
if (!settled) {
|
|
2116
|
-
waiting = true;
|
|
2117
|
-
parked = true;
|
|
2118
|
-
this.running = false;
|
|
2119
|
-
}
|
|
3046
|
+
const message = {
|
|
3047
|
+
caller,
|
|
3048
|
+
handler,
|
|
3049
|
+
payload: payload.fields,
|
|
3050
|
+
reply: false,
|
|
3051
|
+
obligated: false,
|
|
3052
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
2120
3053
|
};
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
if (declared === "coalesce" && coalesce) {
|
|
2124
|
-
const key = coalesce.keys.find((key) => key.message === handler.message_id);
|
|
2125
|
-
const queued = key
|
|
2126
|
-
? actor.mailbox.findIndex((message) => !message.obligated &&
|
|
2127
|
-
message.handler.message_id === handler.message_id &&
|
|
2128
|
-
equals(message.payload[key.param], payload.fields[key.param]))
|
|
2129
|
-
: -1;
|
|
2130
|
-
if (queued >= 0) {
|
|
2131
|
-
const superseded = actor.mailbox[queued];
|
|
2132
|
-
actor.mailbox[queued] = {
|
|
2133
|
-
...superseded,
|
|
2134
|
-
caller,
|
|
2135
|
-
payload: payload.fields,
|
|
2136
|
-
};
|
|
2137
|
-
discardAtReceiver({ kind: "record", typeId: "", fields: superseded.payload }, accepted(false));
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
declared = coalesce.fallback;
|
|
2141
|
-
}
|
|
2142
|
-
if (declared === "drop_new") {
|
|
2143
|
-
discardAtReceiver(payload, accepted(true));
|
|
2144
|
-
return;
|
|
2145
|
-
}
|
|
2146
|
-
// A queued completion call owes its caller a reply, so `drop_old`
|
|
2147
|
-
// evicts only a one-way message and refuses when there is none.
|
|
2148
|
-
const oldest = declared === "drop_old"
|
|
2149
|
-
? actor.mailbox.findIndex((message) => !message.obligated)
|
|
2150
|
-
: -1;
|
|
2151
|
-
if (oldest >= 0) {
|
|
2152
|
-
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
2153
|
-
actor.mailbox.push({
|
|
2154
|
-
caller,
|
|
2155
|
-
handler,
|
|
2156
|
-
payload: payload.fields,
|
|
2157
|
-
reply: false,
|
|
2158
|
-
obligated: false,
|
|
2159
|
-
complete: (_value, _error, drained) => drained?.(null),
|
|
2160
|
-
});
|
|
2161
|
-
this.dispatchActor(actor);
|
|
2162
|
-
discardAtReceiver({ kind: "record", typeId: "", fields: evicted.payload }, accepted(false));
|
|
2163
|
-
return;
|
|
2164
|
-
}
|
|
2165
|
-
if (full) {
|
|
3054
|
+
const status = this.admitActorMessage(actor, message);
|
|
3055
|
+
if (status === "full") {
|
|
2166
3056
|
if (operation.policy === "drop_newest") {
|
|
2167
|
-
|
|
3057
|
+
let settled = false;
|
|
3058
|
+
let waiting = false;
|
|
3059
|
+
this.closeValueAsync(payload, this.pipeFault(act), (fault) => {
|
|
3060
|
+
act.fault ??= fault;
|
|
3061
|
+
settled = true;
|
|
3062
|
+
if (waiting)
|
|
3063
|
+
finish(accepted(true));
|
|
3064
|
+
else
|
|
3065
|
+
this.completeShim(act, term, accepted(true));
|
|
3066
|
+
}, act.context.actor, act.rcOwner);
|
|
3067
|
+
if (!settled) {
|
|
3068
|
+
waiting = true;
|
|
3069
|
+
parked = true;
|
|
3070
|
+
this.running = false;
|
|
3071
|
+
}
|
|
2168
3072
|
return;
|
|
2169
3073
|
}
|
|
2170
3074
|
if (operation.policy === "reject") {
|
|
2171
3075
|
finish(rejected("Full"));
|
|
2172
3076
|
return;
|
|
2173
3077
|
}
|
|
2174
|
-
// The checker already resolved declaration defaults and explicit
|
|
2175
|
-
// overrides; the declaration's own resolution happened above.
|
|
2176
3078
|
if (operation.policy !== "wait")
|
|
2177
3079
|
throw new Error("submission has no admitted policy");
|
|
2178
3080
|
parked = true;
|
|
@@ -2180,20 +3082,10 @@ class ExecutorV1 {
|
|
|
2180
3082
|
actor.admission.push(submit);
|
|
2181
3083
|
return;
|
|
2182
3084
|
}
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
reply: false,
|
|
2188
|
-
obligated: false,
|
|
2189
|
-
complete: (_value, _error, drained) => drained?.(null),
|
|
2190
|
-
});
|
|
2191
|
-
this.trace.snapshot("actor.send", {
|
|
2192
|
-
actor_id: actor.id,
|
|
2193
|
-
handler: handler.name,
|
|
2194
|
-
});
|
|
2195
|
-
this.dispatchActor(actor);
|
|
2196
|
-
finish(accepted(false));
|
|
3085
|
+
if (status === "closed")
|
|
3086
|
+
finish(rejected("Dead"));
|
|
3087
|
+
else
|
|
3088
|
+
finish(accepted(status === "discarded"));
|
|
2197
3089
|
};
|
|
2198
3090
|
submit();
|
|
2199
3091
|
return;
|
|
@@ -2202,17 +3094,35 @@ class ExecutorV1 {
|
|
|
2202
3094
|
const layout = this.pkg.supervisors?.find((layout) => layout.id === operation.supervisor);
|
|
2203
3095
|
if (!layout)
|
|
2204
3096
|
throw new Error("supervisor descriptor is missing");
|
|
3097
|
+
const isPool = layout.children.some((child) => child.is_pool);
|
|
3098
|
+
const count = isPool ? args[args.length - 1] : undefined;
|
|
3099
|
+
if (isPool && count?.kind !== "i64")
|
|
3100
|
+
throw new Error("pool construction has no count");
|
|
3101
|
+
const config = isPool ? args.slice(0, -1) : args;
|
|
3102
|
+
if (count?.kind === "i64" &&
|
|
3103
|
+
(count.value <= 0n || count.value > 2147483647n)) {
|
|
3104
|
+
const fault = {
|
|
3105
|
+
kind: "panic",
|
|
3106
|
+
message: "supervisor pool count must be between 1 and 2147483647",
|
|
3107
|
+
};
|
|
3108
|
+
this.running = false;
|
|
3109
|
+
this.closeValueAsync({ kind: "record", typeId: "", fields: config }, fault.message, (closeFault) => {
|
|
3110
|
+
this.raiseFault(act, combineFaults(fault, closeFault), term.unwind);
|
|
3111
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
3112
|
+
}, act.context.actor, act.rcOwner);
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
const members = count?.kind === "i64" ? Number(count.value) : 1;
|
|
2205
3116
|
const owner = {
|
|
2206
3117
|
id: this.ids.supervisor(),
|
|
2207
3118
|
layout,
|
|
2208
|
-
config
|
|
2209
|
-
children: layout.children.flatMap((
|
|
2210
|
-
specs: layout.children.flatMap((child) => Array.from({ length:
|
|
2211
|
-
offsets: layout.children.map((_child, index) =>
|
|
2212
|
-
.slice(0, index)
|
|
2213
|
-
.reduce((sum, child) => sum + (child.pool_count ?? 1), 0)),
|
|
3119
|
+
config,
|
|
3120
|
+
children: layout.children.flatMap(() => Array.from({ length: members }, () => null)),
|
|
3121
|
+
specs: layout.children.flatMap((child) => Array.from({ length: members }, () => child)),
|
|
3122
|
+
offsets: layout.children.map((_child, index) => index),
|
|
2214
3123
|
recovering: new Set(),
|
|
2215
|
-
restartBusy:
|
|
3124
|
+
restartBusy: true,
|
|
3125
|
+
constructor: null,
|
|
2216
3126
|
pendingRestarts: [],
|
|
2217
3127
|
spent: new Set(),
|
|
2218
3128
|
completed: false,
|
|
@@ -2227,20 +3137,39 @@ class ExecutorV1 {
|
|
|
2227
3137
|
this.trace.allocateId("supervisor", owner.id, act.context.id);
|
|
2228
3138
|
this.trace.snapshot("supervisor.spawn", { supervisor_id: owner.id });
|
|
2229
3139
|
this.running = false;
|
|
2230
|
-
|
|
3140
|
+
const order = this.supervisorStartupOrder(owner);
|
|
3141
|
+
let fault = null;
|
|
3142
|
+
const fail = (cause) => {
|
|
3143
|
+
if (fault)
|
|
3144
|
+
return;
|
|
3145
|
+
fault = cause;
|
|
3146
|
+
owner.closed.push(() => {
|
|
3147
|
+
act.context.cancel = undefined;
|
|
3148
|
+
this.raiseFault(act, fault, term.unwind);
|
|
3149
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
3150
|
+
});
|
|
3151
|
+
if (owner.constructor)
|
|
3152
|
+
owner.constructor.pendingCancellation ??= cause;
|
|
3153
|
+
this.stopSupervisor(owner);
|
|
3154
|
+
};
|
|
3155
|
+
act.context.cancel = (cause = {
|
|
3156
|
+
kind: "panic",
|
|
3157
|
+
message: "construction cancelled",
|
|
3158
|
+
cancelled: true,
|
|
3159
|
+
}) => fail(cause);
|
|
2231
3160
|
const next = () => {
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
this.raiseFault(act, fault, term.unwind);
|
|
2236
|
-
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2237
|
-
});
|
|
2238
|
-
this.stopSupervisor(owner);
|
|
2239
|
-
});
|
|
3161
|
+
const child = order.shift();
|
|
3162
|
+
if (child !== undefined) {
|
|
3163
|
+
this.startChild(owner, child, next, fail);
|
|
2240
3164
|
}
|
|
2241
3165
|
else {
|
|
3166
|
+
act.context.cancel = undefined;
|
|
3167
|
+
owner.restartBusy = false;
|
|
2242
3168
|
this.completeShim(act, term, { kind: "supervisor", id: owner.id });
|
|
2243
3169
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
3170
|
+
const pending = owner.pendingRestarts.shift();
|
|
3171
|
+
if (pending)
|
|
3172
|
+
this.restartChild(owner, pending.child, pending.debts);
|
|
2244
3173
|
}
|
|
2245
3174
|
};
|
|
2246
3175
|
next();
|
|
@@ -2269,7 +3198,7 @@ class ExecutorV1 {
|
|
|
2269
3198
|
this.completeShim(act, term, {
|
|
2270
3199
|
kind: "vector",
|
|
2271
3200
|
elementType: "role",
|
|
2272
|
-
items: Array.from({ length:
|
|
3201
|
+
items: Array.from({ length: owner.children.length }, (_, index) => makeRole(offset + index)),
|
|
2273
3202
|
});
|
|
2274
3203
|
return;
|
|
2275
3204
|
}
|
|
@@ -2279,12 +3208,16 @@ class ExecutorV1 {
|
|
|
2279
3208
|
case "await_restarted": {
|
|
2280
3209
|
const target = args[0];
|
|
2281
3210
|
const role = "id" in target ? this.roles.get(target.id) : undefined;
|
|
2282
|
-
if (!role)
|
|
2283
|
-
|
|
2284
|
-
|
|
3211
|
+
if (!role) {
|
|
3212
|
+
if (target.kind !== "actor" && target.kind !== "supervisor")
|
|
3213
|
+
throw new Error("restarted requires an actor or supervisor handle");
|
|
3214
|
+
this.completeShim(act, term, UNIT);
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
if (this.roleRestartPending(role)) {
|
|
2285
3218
|
this.running = false;
|
|
2286
3219
|
const settled = () => {
|
|
2287
|
-
if (this.
|
|
3220
|
+
if (this.roleRestartPending(role)) {
|
|
2288
3221
|
role.waiting.push(settled);
|
|
2289
3222
|
return;
|
|
2290
3223
|
}
|
|
@@ -2324,7 +3257,10 @@ class ExecutorV1 {
|
|
|
2324
3257
|
if (target.kind === "actor") {
|
|
2325
3258
|
const actor = this.actorFor(target);
|
|
2326
3259
|
const complete = () => {
|
|
2327
|
-
|
|
3260
|
+
// A crash was reported when it happened and already counts toward
|
|
3261
|
+
// the exit status; only a fault raised while the actor was being
|
|
3262
|
+
// stopped passes to the caller that waits for it.
|
|
3263
|
+
const fault = actor.closingFault;
|
|
2328
3264
|
if (actor.closeFaultDebt !== undefined)
|
|
2329
3265
|
this.faultDebts.delete(actor.closeFaultDebt);
|
|
2330
3266
|
if (fault)
|
|
@@ -2366,9 +3302,21 @@ class ExecutorV1 {
|
|
|
2366
3302
|
id: act.context.actor.id,
|
|
2367
3303
|
});
|
|
2368
3304
|
return;
|
|
2369
|
-
|
|
2370
|
-
throw new Error(
|
|
3305
|
+
case "local_observation":
|
|
3306
|
+
throw new Error("actor observations are not admitted in the VM");
|
|
3307
|
+
default: {
|
|
3308
|
+
const exhaustive = operation;
|
|
3309
|
+
throw new Error("unhandled actor operation: " + String(exhaustive));
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
scopeDeadline(act, expected) {
|
|
3314
|
+
for (let frame = act; frame?.context === act.context; frame = frame.caller) {
|
|
3315
|
+
for (const group of frame.scopes.values())
|
|
3316
|
+
if (group.deadline && (!expected || group.deadline === expected))
|
|
3317
|
+
return group.deadline;
|
|
2371
3318
|
}
|
|
3319
|
+
return undefined;
|
|
2372
3320
|
}
|
|
2373
3321
|
requestDeadline(act) {
|
|
2374
3322
|
let deadline = act.context.requestDeadline;
|
|
@@ -2380,11 +3328,12 @@ class ExecutorV1 {
|
|
|
2380
3328
|
}
|
|
2381
3329
|
return deadline;
|
|
2382
3330
|
}
|
|
2383
|
-
ask(protocol, inputs, complete, shapes) {
|
|
3331
|
+
ask(protocol, inputs, complete, shapes, selectedTarget) {
|
|
2384
3332
|
let settled = false;
|
|
2385
3333
|
let admitted = false;
|
|
2386
3334
|
let closing = false;
|
|
2387
3335
|
const owner = this.current.context.actor;
|
|
3336
|
+
const rcOwner = this.current.rcOwner;
|
|
2388
3337
|
const deadline = this.requestDeadline(this.current);
|
|
2389
3338
|
let cancelTimer;
|
|
2390
3339
|
let message;
|
|
@@ -2406,7 +3355,7 @@ class ExecutorV1 {
|
|
|
2406
3355
|
this.crashActor(owner, fault);
|
|
2407
3356
|
else if (fault)
|
|
2408
3357
|
this.rootFault ??= fault;
|
|
2409
|
-
}, admitted ? receiver : owner);
|
|
3358
|
+
}, admitted ? receiver : owner, admitted ? (receiver?.rcOwner ?? null) : rcOwner);
|
|
2410
3359
|
}
|
|
2411
3360
|
else
|
|
2412
3361
|
drained?.(null);
|
|
@@ -2430,7 +3379,7 @@ class ExecutorV1 {
|
|
|
2430
3379
|
closeFault = fault;
|
|
2431
3380
|
for (const waiter of closeWaiters.splice(0))
|
|
2432
3381
|
waiter(fault);
|
|
2433
|
-
}, owner);
|
|
3382
|
+
}, owner, rcOwner);
|
|
2434
3383
|
};
|
|
2435
3384
|
const reject = (reason) => {
|
|
2436
3385
|
if (settled || closing)
|
|
@@ -2463,7 +3412,10 @@ class ExecutorV1 {
|
|
|
2463
3412
|
const handler = actor.layout.handlers.find((handler) => handler.message_id === protocol.message);
|
|
2464
3413
|
if (!handler)
|
|
2465
3414
|
throw new Error(`actor ${actor.id} has no message ${protocol.message}`);
|
|
2466
|
-
|
|
3415
|
+
selectedTarget?.(actor);
|
|
3416
|
+
if (settled || closing)
|
|
3417
|
+
return;
|
|
3418
|
+
const full = actor.layout.mailbox_capacity != null &&
|
|
2467
3419
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
2468
3420
|
if (full) {
|
|
2469
3421
|
if (protocol.policy === "reject") {
|
|
@@ -2552,6 +3504,114 @@ class ExecutorV1 {
|
|
|
2552
3504
|
closeRequest(done);
|
|
2553
3505
|
};
|
|
2554
3506
|
}
|
|
3507
|
+
admitActorMessage(actor, message) {
|
|
3508
|
+
if (!actor.alive || actor.closing)
|
|
3509
|
+
return "closed";
|
|
3510
|
+
const full = actor.layout.mailbox_capacity != null &&
|
|
3511
|
+
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3512
|
+
let declared = full ? actor.layout.overflow : null;
|
|
3513
|
+
const discard = (lost) => {
|
|
3514
|
+
actor.disposals.push({
|
|
3515
|
+
kind: "record",
|
|
3516
|
+
typeId: "",
|
|
3517
|
+
fields: lost.payload,
|
|
3518
|
+
});
|
|
3519
|
+
lost.timer?.discarded();
|
|
3520
|
+
this.dispatchActor(actor);
|
|
3521
|
+
};
|
|
3522
|
+
const coalesce = actor.layout.coalesce;
|
|
3523
|
+
if (declared === "coalesce" && coalesce) {
|
|
3524
|
+
const key = coalesce.keys.find((key) => key.message === message.handler.message_id);
|
|
3525
|
+
const queued = key
|
|
3526
|
+
? actor.mailbox.findIndex((queued) => !queued.obligated &&
|
|
3527
|
+
queued.handler.message_id === message.handler.message_id &&
|
|
3528
|
+
equals(queued.payload[key.param], message.payload[key.param]))
|
|
3529
|
+
: -1;
|
|
3530
|
+
if (queued >= 0) {
|
|
3531
|
+
const superseded = actor.mailbox[queued];
|
|
3532
|
+
actor.mailbox[queued] = message;
|
|
3533
|
+
discard(superseded);
|
|
3534
|
+
return "accepted";
|
|
3535
|
+
}
|
|
3536
|
+
declared = coalesce.fallback;
|
|
3537
|
+
}
|
|
3538
|
+
if (declared === "drop_new") {
|
|
3539
|
+
discard(message);
|
|
3540
|
+
return "discarded";
|
|
3541
|
+
}
|
|
3542
|
+
const oldest = declared === "drop_old"
|
|
3543
|
+
? actor.mailbox.findIndex((queued) => !queued.obligated)
|
|
3544
|
+
: -1;
|
|
3545
|
+
if (oldest >= 0) {
|
|
3546
|
+
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
3547
|
+
actor.mailbox.push(message);
|
|
3548
|
+
discard(evicted);
|
|
3549
|
+
return "accepted";
|
|
3550
|
+
}
|
|
3551
|
+
if (full)
|
|
3552
|
+
return "full";
|
|
3553
|
+
actor.mailbox.push(message);
|
|
3554
|
+
this.trace.snapshot("actor.send", {
|
|
3555
|
+
actor_id: actor.id,
|
|
3556
|
+
handler: message.handler.name,
|
|
3557
|
+
});
|
|
3558
|
+
this.dispatchActor(actor);
|
|
3559
|
+
return "accepted";
|
|
3560
|
+
}
|
|
3561
|
+
completeRoot() {
|
|
3562
|
+
this.rootComplete = true;
|
|
3563
|
+
// Quiesce actor timers before draining accepted work; sleeps still wake.
|
|
3564
|
+
for (const actor of this.actors.values())
|
|
3565
|
+
this.cancelActorTimers(actor);
|
|
3566
|
+
}
|
|
3567
|
+
startPeriodicTimers(actor) {
|
|
3568
|
+
if (this.rootComplete || actor.closing)
|
|
3569
|
+
return;
|
|
3570
|
+
for (const handler of actor.layout.handlers) {
|
|
3571
|
+
if (handler.every_ns == null)
|
|
3572
|
+
continue;
|
|
3573
|
+
const interval = Math.floor(handler.every_ns / 1_000_000);
|
|
3574
|
+
let next = this.trace.virtualTimeMs + interval;
|
|
3575
|
+
let disarm = null;
|
|
3576
|
+
const timer = {
|
|
3577
|
+
cancel: () => {
|
|
3578
|
+
disarm?.();
|
|
3579
|
+
disarm = null;
|
|
3580
|
+
actor.timers.delete(timer);
|
|
3581
|
+
},
|
|
3582
|
+
};
|
|
3583
|
+
const fire = () => {
|
|
3584
|
+
disarm = null;
|
|
3585
|
+
if (!actor.timers.has(timer))
|
|
3586
|
+
return;
|
|
3587
|
+
this.admitActorMessage(actor, {
|
|
3588
|
+
caller: { kind: "Timer" },
|
|
3589
|
+
handler,
|
|
3590
|
+
payload: [],
|
|
3591
|
+
reply: false,
|
|
3592
|
+
obligated: false,
|
|
3593
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
3594
|
+
});
|
|
3595
|
+
if (!actor.timers.has(timer))
|
|
3596
|
+
return;
|
|
3597
|
+
next += interval;
|
|
3598
|
+
if (next <= this.trace.virtualTimeMs)
|
|
3599
|
+
next +=
|
|
3600
|
+
(Math.floor((this.trace.virtualTimeMs - next) / interval) + 1) *
|
|
3601
|
+
interval;
|
|
3602
|
+
arm();
|
|
3603
|
+
};
|
|
3604
|
+
const arm = () => {
|
|
3605
|
+
disarm = this.scheduler.after(BigInt(Math.round((next - this.trace.virtualTimeMs) * 1_000_000)), fire);
|
|
3606
|
+
};
|
|
3607
|
+
actor.timers.add(timer);
|
|
3608
|
+
arm();
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
cancelActorTimers(actor) {
|
|
3612
|
+
for (const timer of [...actor.timers])
|
|
3613
|
+
timer.cancel();
|
|
3614
|
+
}
|
|
2555
3615
|
dispatchActor(actor) {
|
|
2556
3616
|
if (actor.busy || !actor.alive || actor.drainingMailbox)
|
|
2557
3617
|
return;
|
|
@@ -2587,13 +3647,21 @@ class ExecutorV1 {
|
|
|
2587
3647
|
this.dispatchActor(actor);
|
|
2588
3648
|
return;
|
|
2589
3649
|
}
|
|
2590
|
-
const message = actor.mailbox
|
|
3650
|
+
const message = actor.mailbox[0];
|
|
2591
3651
|
if (!message || !actor.alive) {
|
|
2592
3652
|
actor.busy = false;
|
|
2593
3653
|
if (actor.closing && actor.alive)
|
|
2594
3654
|
this.dispatchActor(actor);
|
|
2595
3655
|
return;
|
|
2596
3656
|
}
|
|
3657
|
+
// An overdue claim can synchronously begin terminal cleanup. Keep the
|
|
3658
|
+
// payload in its mailbox until that claim succeeds.
|
|
3659
|
+
if (message.timer && !message.timer.started()) {
|
|
3660
|
+
actor.busy = false;
|
|
3661
|
+
this.dispatchActor(actor);
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
actor.mailbox.shift();
|
|
2597
3665
|
for (const admit of actor.admission.splice(0))
|
|
2598
3666
|
admit();
|
|
2599
3667
|
if (message.requestDeadline !== undefined &&
|
|
@@ -2623,7 +3691,7 @@ class ExecutorV1 {
|
|
|
2623
3691
|
const drained = (fault) => {
|
|
2624
3692
|
actor.busy = false;
|
|
2625
3693
|
if (fault)
|
|
2626
|
-
this.crashActor(actor, fault);
|
|
3694
|
+
this.crashActor(actor, fault, message.handler);
|
|
2627
3695
|
else
|
|
2628
3696
|
this.dispatchActor(actor);
|
|
2629
3697
|
};
|
|
@@ -2639,8 +3707,9 @@ class ExecutorV1 {
|
|
|
2639
3707
|
else {
|
|
2640
3708
|
this.crashActor(actor, {
|
|
2641
3709
|
kind: "panic",
|
|
3710
|
+
handlerFailed: true,
|
|
2642
3711
|
message: "unhandled declared handler failure",
|
|
2643
|
-
});
|
|
3712
|
+
}, message.handler);
|
|
2644
3713
|
return;
|
|
2645
3714
|
}
|
|
2646
3715
|
}
|
|
@@ -2667,7 +3736,7 @@ class ExecutorV1 {
|
|
|
2667
3736
|
this.dispatchActor(actor);
|
|
2668
3737
|
}
|
|
2669
3738
|
else {
|
|
2670
|
-
this.crashActor(actor, fault);
|
|
3739
|
+
this.crashActor(actor, fault, message.handler);
|
|
2671
3740
|
message.complete(null, "Trapped");
|
|
2672
3741
|
}
|
|
2673
3742
|
},
|
|
@@ -2676,7 +3745,12 @@ class ExecutorV1 {
|
|
|
2676
3745
|
this.runFrame(frame);
|
|
2677
3746
|
});
|
|
2678
3747
|
}
|
|
2679
|
-
crashActor(actor, fault) {
|
|
3748
|
+
crashActor(actor, fault, handler) {
|
|
3749
|
+
this.reportFault(fault);
|
|
3750
|
+
if (handler) {
|
|
3751
|
+
const context = `${actor.layout.name}.${handler.name}`;
|
|
3752
|
+
this.trace.writeStderr(`hew: actor crash in ${context} (actor ${actor.identity}): ${faultInfo(fault)[1]}\n`, null);
|
|
3753
|
+
}
|
|
2680
3754
|
const debt = ++this.nextFaultDebt;
|
|
2681
3755
|
this.faultDebts.add(debt);
|
|
2682
3756
|
if (actor.supervisor)
|
|
@@ -2684,6 +3758,7 @@ class ExecutorV1 {
|
|
|
2684
3758
|
actor.crashing ??= fault;
|
|
2685
3759
|
actor.alive = false;
|
|
2686
3760
|
actor.busy = false;
|
|
3761
|
+
this.cancelActorTimers(actor);
|
|
2687
3762
|
this.trace.snapshot("actor.crash", {
|
|
2688
3763
|
actor_id: actor.id,
|
|
2689
3764
|
message: this.faultText(fault),
|
|
@@ -2693,47 +3768,114 @@ class ExecutorV1 {
|
|
|
2693
3768
|
actor.mailboxDrained.push(() => finished(action));
|
|
2694
3769
|
return;
|
|
2695
3770
|
}
|
|
2696
|
-
this.closeValueAsync(actor.state, this.faultText(fault), () =>
|
|
2697
|
-
actor.
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
3771
|
+
this.closeValueAsync(actor.state, this.faultText(fault), (stateFault) => {
|
|
3772
|
+
actor.closingFault = combineFaults(actor.closingFault, stateFault);
|
|
3773
|
+
fault = combineFaults(fault, stateFault);
|
|
3774
|
+
actor.crashing = fault;
|
|
3775
|
+
this.reportFault(fault);
|
|
3776
|
+
this.drainMailbox(actor, this.faultText(fault), (payloadFault) => {
|
|
3777
|
+
actor.closingFault = combineFaults(actor.closingFault, payloadFault);
|
|
3778
|
+
fault = combineFaults(fault, payloadFault);
|
|
3779
|
+
actor.crashing = fault;
|
|
3780
|
+
this.reportFault(fault);
|
|
3781
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
3782
|
+
actor.closingFault = combineFaults(actor.closingFault, sweepFault);
|
|
3783
|
+
actor.crashing = combineFaults(actor.crashing, sweepFault);
|
|
3784
|
+
this.reportFault(actor.crashing);
|
|
3785
|
+
if (actor.closingFault) {
|
|
3786
|
+
this.reportFault(actor.closingFault);
|
|
3787
|
+
}
|
|
3788
|
+
actor.completed = true;
|
|
3789
|
+
for (const wake of actor.closed.splice(0))
|
|
3790
|
+
wake();
|
|
3791
|
+
for (const admit of actor.admission.splice(0))
|
|
3792
|
+
admit();
|
|
3793
|
+
if (actor.supervisor) {
|
|
3794
|
+
const { owner, child } = actor.supervisor;
|
|
3795
|
+
if (action === "Restart")
|
|
3796
|
+
this.restartChild(owner, child, [debt]);
|
|
3797
|
+
else if (action === "Escalate")
|
|
3798
|
+
this.escalateSupervisor(owner, [debt]);
|
|
3799
|
+
else {
|
|
3800
|
+
owner.spent.add(child);
|
|
3801
|
+
owner.recovering.delete(child);
|
|
3802
|
+
this.wakeRoles();
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
});
|
|
3806
|
+
});
|
|
3807
|
+
}, actor);
|
|
2715
3808
|
};
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3809
|
+
const runHook = () => {
|
|
3810
|
+
if (actor.layout.crash != null) {
|
|
3811
|
+
const [code, message] = faultInfo(fault);
|
|
3812
|
+
const info = {
|
|
3813
|
+
kind: "record",
|
|
3814
|
+
typeId: this.aggregateName(actor.layout.crash_info),
|
|
3815
|
+
shape: actor.layout.crash_info,
|
|
3816
|
+
fields: [
|
|
3817
|
+
{
|
|
3818
|
+
kind: "enum",
|
|
3819
|
+
typeId: this.variantName(actor.layout.crash_kind),
|
|
3820
|
+
shape: actor.layout.crash_kind,
|
|
3821
|
+
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 214
|
|
3822
|
+
? "CrashKindTimerOverdue"
|
|
3823
|
+
: code === 213
|
|
3824
|
+
? "CrashKindHandlerFailed"
|
|
3825
|
+
: "CrashKindFault"),
|
|
3826
|
+
payload: [],
|
|
3827
|
+
},
|
|
3828
|
+
stringValue(String(message)),
|
|
3829
|
+
],
|
|
3830
|
+
};
|
|
3831
|
+
let cancelTimer;
|
|
3832
|
+
const complete = (action) => {
|
|
3833
|
+
cancelTimer?.();
|
|
3834
|
+
actor.terminalHook = null;
|
|
3835
|
+
finished(action);
|
|
3836
|
+
};
|
|
3837
|
+
const hook = this.invokeFrame(actor, actor.layout.crash, [actor.state, info], (value) => {
|
|
3838
|
+
const shape = this.pkg.variants[actor.layout.crash_action];
|
|
3839
|
+
const action = value.kind === "enum" ? shape?.cases[value.tag]?.name : undefined;
|
|
3840
|
+
if (!action)
|
|
3841
|
+
throw new Error("crash hook returned no declared action");
|
|
3842
|
+
complete(action);
|
|
3843
|
+
}, (hookFault) => {
|
|
3844
|
+
actor.closingFault = combineFaults(actor.closingFault, hookFault);
|
|
3845
|
+
fault = combineFaults(fault, hookFault);
|
|
3846
|
+
actor.crashing = fault;
|
|
3847
|
+
this.reportFault(fault);
|
|
3848
|
+
complete("Restart");
|
|
3849
|
+
});
|
|
3850
|
+
actor.terminalHook = hook;
|
|
3851
|
+
const parent = actor.supervisor;
|
|
3852
|
+
const stopNs = parent
|
|
3853
|
+
? this.childStopBudget(parent.owner, parent.child)
|
|
3854
|
+
: DEFAULT_ACTOR_STOP_NS;
|
|
3855
|
+
hook.requestDeadline = this.trace.virtualTimeMs + stopNs / 1_000_000;
|
|
3856
|
+
const expire = () => {
|
|
3857
|
+
hook.pendingCancellation ??= {
|
|
3858
|
+
kind: "panic",
|
|
3859
|
+
message: "crash hook deadline elapsed",
|
|
3860
|
+
deadline: true,
|
|
3861
|
+
};
|
|
3862
|
+
hook.cancel?.(hook.pendingCancellation);
|
|
3863
|
+
};
|
|
3864
|
+
if (stopNs <= 0)
|
|
3865
|
+
expire();
|
|
3866
|
+
else
|
|
3867
|
+
cancelTimer = this.scheduler.after(BigInt(stopNs), expire);
|
|
3868
|
+
}
|
|
3869
|
+
else
|
|
3870
|
+
finished("Restart");
|
|
3871
|
+
};
|
|
3872
|
+
this.drainMailbox(actor, this.faultText(fault), (cleanupFault) => {
|
|
3873
|
+
actor.closingFault = combineFaults(actor.closingFault, cleanupFault);
|
|
3874
|
+
fault = combineFaults(fault, cleanupFault);
|
|
3875
|
+
actor.crashing = fault;
|
|
3876
|
+
this.reportFault(fault);
|
|
3877
|
+
runHook();
|
|
3878
|
+
});
|
|
2737
3879
|
}
|
|
2738
3880
|
supervisorFor(value) {
|
|
2739
3881
|
if (value.kind !== "supervisor")
|
|
@@ -2746,9 +3888,42 @@ class ExecutorV1 {
|
|
|
2746
3888
|
throw new Error("supervisor does not exist");
|
|
2747
3889
|
return owner;
|
|
2748
3890
|
}
|
|
3891
|
+
supervisorStartupOrder(owner) {
|
|
3892
|
+
const declarations = owner.layout.startup_order ??
|
|
3893
|
+
owner.layout.children.map((_child, index) => index);
|
|
3894
|
+
return declarations.flatMap((declaration) => {
|
|
3895
|
+
const offset = owner.offsets[declaration];
|
|
3896
|
+
const end = owner.offsets[declaration + 1] ?? owner.children.length;
|
|
3897
|
+
return Array.from({ length: end - offset }, (_value, index) => offset + index);
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
2749
3900
|
startChild(owner, child, done, failed) {
|
|
2750
3901
|
const spec = owner.specs[child];
|
|
2751
|
-
this.
|
|
3902
|
+
const arity = this.functionAt(spec.spawn).params.length;
|
|
3903
|
+
// Packages predating sibling wiring have config-only constructors. Their
|
|
3904
|
+
// actual callable signature, rather than a source name, selects that ABI.
|
|
3905
|
+
const args = arity === owner.config.length + 1
|
|
3906
|
+
? [{ kind: "supervisor", id: owner.id }, ...owner.config]
|
|
3907
|
+
: arity === owner.config.length
|
|
3908
|
+
? owner.config
|
|
3909
|
+
: null;
|
|
3910
|
+
if (!args)
|
|
3911
|
+
throw new Error("supervisor constructor signature does not match its config");
|
|
3912
|
+
owner.constructor = this.invokeFrame(null, spec.spawn, args, (value) => {
|
|
3913
|
+
const cancellation = owner.constructor?.pendingCancellation;
|
|
3914
|
+
owner.constructor = null;
|
|
3915
|
+
if (!owner.alive) {
|
|
3916
|
+
// The result is still owned here. Its declaration slot hands it to
|
|
3917
|
+
// canonical reverse-order teardown, including the configured budget.
|
|
3918
|
+
owner.children[child] = value;
|
|
3919
|
+
failed(cancellation ?? {
|
|
3920
|
+
kind: "panic",
|
|
3921
|
+
message: "supervisor construction cancelled",
|
|
3922
|
+
cancelled: true,
|
|
3923
|
+
});
|
|
3924
|
+
this.finishSupervisorStop(owner);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
2752
3927
|
owner.children[child] = value;
|
|
2753
3928
|
if (value.kind === "actor")
|
|
2754
3929
|
this.actorFor(value).supervisor = { owner, child };
|
|
@@ -2761,7 +3936,15 @@ class ExecutorV1 {
|
|
|
2761
3936
|
child,
|
|
2762
3937
|
});
|
|
2763
3938
|
done();
|
|
2764
|
-
},
|
|
3939
|
+
}, (fault) => {
|
|
3940
|
+
const stopped = !owner.alive;
|
|
3941
|
+
owner.constructor = null;
|
|
3942
|
+
// The initial caller still needs one terminal notification if the
|
|
3943
|
+
// supervisor was stopped directly rather than through caller cancel.
|
|
3944
|
+
failed(fault);
|
|
3945
|
+
if (stopped)
|
|
3946
|
+
this.finishSupervisorStop(owner);
|
|
3947
|
+
});
|
|
2765
3948
|
}
|
|
2766
3949
|
roleOwner(role) {
|
|
2767
3950
|
const parentRole = this.roles.get(role.ownerId);
|
|
@@ -2786,6 +3969,23 @@ class ExecutorV1 {
|
|
|
2786
3969
|
return (!!owner?.alive &&
|
|
2787
3970
|
(owner.recovering.has(role.child) || owner.children[role.child] === null));
|
|
2788
3971
|
}
|
|
3972
|
+
roleRestartPending(role) {
|
|
3973
|
+
const parent = this.roles.get(role.ownerId);
|
|
3974
|
+
if (parent && this.roleRestartPending(parent))
|
|
3975
|
+
return true;
|
|
3976
|
+
if (this.rolePending(role))
|
|
3977
|
+
return true;
|
|
3978
|
+
const value = this.roleValue(role) ?? role.latest;
|
|
3979
|
+
if (value?.kind === "actor") {
|
|
3980
|
+
const actor = this.actorFor(value);
|
|
3981
|
+
return !actor.completed && (actor.closing || !actor.alive);
|
|
3982
|
+
}
|
|
3983
|
+
if (value?.kind === "supervisor") {
|
|
3984
|
+
const owner = this.supervisorFor(value);
|
|
3985
|
+
return !owner.completed && !owner.alive;
|
|
3986
|
+
}
|
|
3987
|
+
return false;
|
|
3988
|
+
}
|
|
2789
3989
|
wakeRoles() {
|
|
2790
3990
|
for (const role of this.roles.values()) {
|
|
2791
3991
|
this.roleValue(role);
|
|
@@ -2839,7 +4039,7 @@ class ExecutorV1 {
|
|
|
2839
4039
|
stop();
|
|
2840
4040
|
});
|
|
2841
4041
|
};
|
|
2842
|
-
const starting =
|
|
4042
|
+
const starting = this.supervisorStartupOrder(owner).filter((index) => affected.includes(index));
|
|
2843
4043
|
const start = () => {
|
|
2844
4044
|
if (!owner.alive) {
|
|
2845
4045
|
owner.restartBusy = false;
|
|
@@ -2918,6 +4118,16 @@ class ExecutorV1 {
|
|
|
2918
4118
|
else if (value.kind === "supervisor")
|
|
2919
4119
|
this.stopSupervisor(this.supervisorFor(value), true);
|
|
2920
4120
|
}
|
|
4121
|
+
childStopBudget(owner, child) {
|
|
4122
|
+
const spec = owner.specs[child];
|
|
4123
|
+
if (spec.stop_config != null) {
|
|
4124
|
+
const duration = owner.config[spec.stop_config];
|
|
4125
|
+
if (duration?.kind !== "i64" || duration.value < 0n)
|
|
4126
|
+
throw new Error("child stop budget is not a non-negative duration");
|
|
4127
|
+
return Number(duration.value);
|
|
4128
|
+
}
|
|
4129
|
+
return (spec.stop_ns ?? ("actor" in spec.role ? DEFAULT_ACTOR_STOP_NS : null));
|
|
4130
|
+
}
|
|
2921
4131
|
stopSupervisor(owner, force = false) {
|
|
2922
4132
|
if (force) {
|
|
2923
4133
|
owner.terminating = true;
|
|
@@ -2928,6 +4138,18 @@ class ExecutorV1 {
|
|
|
2928
4138
|
if (!owner.alive)
|
|
2929
4139
|
return;
|
|
2930
4140
|
owner.alive = false;
|
|
4141
|
+
if (owner.constructor) {
|
|
4142
|
+
owner.constructor.pendingCancellation ??= {
|
|
4143
|
+
kind: "panic",
|
|
4144
|
+
message: "supervisor construction cancelled",
|
|
4145
|
+
cancelled: true,
|
|
4146
|
+
};
|
|
4147
|
+
owner.constructor.cancel?.(owner.constructor.pendingCancellation);
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
this.finishSupervisorStop(owner);
|
|
4151
|
+
}
|
|
4152
|
+
finishSupervisorStop(owner) {
|
|
2931
4153
|
let childIndex = owner.children.length - 1;
|
|
2932
4154
|
const next = () => {
|
|
2933
4155
|
owner.teardownCancelTimer?.();
|
|
@@ -2937,12 +4159,7 @@ class ExecutorV1 {
|
|
|
2937
4159
|
const index = childIndex--;
|
|
2938
4160
|
const child = owner.children[index];
|
|
2939
4161
|
owner.teardownChild = child;
|
|
2940
|
-
const
|
|
2941
|
-
const stopNs = spec.stop_ns === undefined
|
|
2942
|
-
? "actor" in spec.role
|
|
2943
|
-
? 5_000_000_000
|
|
2944
|
-
: null
|
|
2945
|
-
: spec.stop_ns;
|
|
4162
|
+
const stopNs = this.childStopBudget(owner, index);
|
|
2946
4163
|
if (!owner.terminating && stopNs !== null && stopNs > 0) {
|
|
2947
4164
|
owner.teardownCancelTimer = this.scheduler.after(BigInt(stopNs), () => this.terminateChild(child));
|
|
2948
4165
|
}
|
|
@@ -2950,9 +4167,11 @@ class ExecutorV1 {
|
|
|
2950
4167
|
return;
|
|
2951
4168
|
}
|
|
2952
4169
|
this.closeValueAsync({ kind: "record", typeId: "", fields: owner.config }, null, (fault) => {
|
|
2953
|
-
|
|
2954
|
-
|
|
4170
|
+
const debt = fault ? ++this.nextFaultDebt : undefined;
|
|
4171
|
+
if (debt !== undefined)
|
|
4172
|
+
this.faultDebts.add(debt);
|
|
2955
4173
|
owner.completed = true;
|
|
4174
|
+
this.settleSupervisedEnd(owner, fault ? "Crashed" : owner.terminating ? "Terminated" : "Stopped", debt === undefined ? [] : [debt]);
|
|
2956
4175
|
for (const wake of owner.closed.splice(0))
|
|
2957
4176
|
wake();
|
|
2958
4177
|
this.wakeRoles();
|
|
@@ -2987,6 +4206,15 @@ class ExecutorV1 {
|
|
|
2987
4206
|
if (actor.closing || actor.completed)
|
|
2988
4207
|
return;
|
|
2989
4208
|
actor.closing = true;
|
|
4209
|
+
this.cancelActorTimers(actor);
|
|
4210
|
+
if (actor.constructing && actor.active) {
|
|
4211
|
+
actor.active.pendingCancellation ??= {
|
|
4212
|
+
kind: "panic",
|
|
4213
|
+
message: "actor construction stopped",
|
|
4214
|
+
cancelled: true,
|
|
4215
|
+
};
|
|
4216
|
+
actor.active.cancel?.(actor.active.pendingCancellation);
|
|
4217
|
+
}
|
|
2990
4218
|
// Admission closes immediately. The active turn and every accepted queued
|
|
2991
4219
|
// turn finish before stop hooks and state release.
|
|
2992
4220
|
this.dispatchActor(actor);
|
|
@@ -2998,8 +4226,17 @@ class ExecutorV1 {
|
|
|
2998
4226
|
return;
|
|
2999
4227
|
actor.closing = true;
|
|
3000
4228
|
actor.terminating = true;
|
|
3001
|
-
|
|
3002
|
-
actor.
|
|
4229
|
+
this.cancelActorTimers(actor);
|
|
4230
|
+
for (const context of [actor.active, actor.terminalHook]) {
|
|
4231
|
+
if (!context)
|
|
4232
|
+
continue;
|
|
4233
|
+
context.pendingCancellation ??= {
|
|
4234
|
+
kind: "panic",
|
|
4235
|
+
message: "actor terminated",
|
|
4236
|
+
cancelled: true,
|
|
4237
|
+
};
|
|
4238
|
+
context.cancel?.(context.pendingCancellation);
|
|
4239
|
+
}
|
|
3003
4240
|
this.dispatchActor(actor);
|
|
3004
4241
|
for (const admit of actor.admission.splice(0))
|
|
3005
4242
|
admit();
|
|
@@ -3010,11 +4247,11 @@ class ExecutorV1 {
|
|
|
3010
4247
|
const next = () => {
|
|
3011
4248
|
const hook = actor.terminating ? undefined : hooks.shift();
|
|
3012
4249
|
if (hook !== undefined) {
|
|
3013
|
-
actor.
|
|
3014
|
-
actor.
|
|
4250
|
+
actor.terminalHook = this.invokeFrame(actor, hook, [actor.state], () => {
|
|
4251
|
+
actor.terminalHook = null;
|
|
3015
4252
|
next();
|
|
3016
4253
|
}, (fault) => {
|
|
3017
|
-
actor.
|
|
4254
|
+
actor.terminalHook = null;
|
|
3018
4255
|
if (!(actor.terminating && fault.kind === "panic" && fault.cancelled))
|
|
3019
4256
|
actor.closingFault = combineFaults(actor.closingFault, fault);
|
|
3020
4257
|
hooks.length = 0;
|
|
@@ -3024,31 +4261,37 @@ class ExecutorV1 {
|
|
|
3024
4261
|
else {
|
|
3025
4262
|
this.closeValueAsync(actor.state, this.faultText(actor.closingFault), (fault) => {
|
|
3026
4263
|
actor.closingFault = combineFaults(actor.closingFault, fault);
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
actor.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
: actor.
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
4264
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
4265
|
+
actor.closingFault = combineFaults(actor.closingFault, sweepFault);
|
|
4266
|
+
// Stopped cleanup belongs to the completion barrier, independently
|
|
4267
|
+
// of handler-crash debt and supervisor restart decisions.
|
|
4268
|
+
actor.crashing = null;
|
|
4269
|
+
if (actor.closingFault) {
|
|
4270
|
+
this.reportFault(actor.closingFault);
|
|
4271
|
+
actor.closeFaultDebt = ++this.nextFaultDebt;
|
|
4272
|
+
this.faultDebts.add(actor.closeFaultDebt);
|
|
4273
|
+
}
|
|
4274
|
+
actor.completed = true;
|
|
4275
|
+
actor.alive = false;
|
|
4276
|
+
actor.busy = false;
|
|
4277
|
+
this.trace.snapshot(actor.terminating ? "actor.terminate" : "actor.stop", { actor_id: actor.id });
|
|
4278
|
+
this.settleSupervisedEnd(actor, actor.closingFault
|
|
4279
|
+
? "Crashed"
|
|
4280
|
+
: actor.terminating
|
|
4281
|
+
? "Terminated"
|
|
4282
|
+
: "Stopped", actor.closeFaultDebt === undefined
|
|
4283
|
+
? []
|
|
4284
|
+
: [actor.closeFaultDebt]);
|
|
4285
|
+
for (const wake of actor.closed.splice(0))
|
|
4286
|
+
wake();
|
|
4287
|
+
});
|
|
3045
4288
|
}, actor);
|
|
3046
4289
|
}
|
|
3047
4290
|
};
|
|
3048
4291
|
next();
|
|
3049
4292
|
}
|
|
3050
|
-
settleSupervisedEnd(
|
|
3051
|
-
const parent =
|
|
4293
|
+
settleSupervisedEnd(subject, reason, debts) {
|
|
4294
|
+
const parent = subject.supervisor;
|
|
3052
4295
|
if (!parent ||
|
|
3053
4296
|
!parent.owner.alive ||
|
|
3054
4297
|
parent.owner.recovering.has(parent.child))
|
|
@@ -3057,7 +4300,7 @@ class ExecutorV1 {
|
|
|
3057
4300
|
const policy = owner.specs[child].restart;
|
|
3058
4301
|
if (policy === "permanent" ||
|
|
3059
4302
|
(policy === "transient" && reason !== "Stopped")) {
|
|
3060
|
-
this.restartChild(owner, child,
|
|
4303
|
+
this.restartChild(owner, child, debts);
|
|
3061
4304
|
}
|
|
3062
4305
|
else {
|
|
3063
4306
|
owner.spent.add(child);
|
|
@@ -3065,6 +4308,16 @@ class ExecutorV1 {
|
|
|
3065
4308
|
}
|
|
3066
4309
|
}
|
|
3067
4310
|
// ── exit and faults ──────────────────────────────────────────────────────
|
|
4311
|
+
reportFault(fault) {
|
|
4312
|
+
const diagnostics = [fault.primary ?? fault, ...(fault.secondary ?? [])];
|
|
4313
|
+
diagnostics.forEach((diagnostic, index) => {
|
|
4314
|
+
const origin = diagnostic.primary ?? diagnostic;
|
|
4315
|
+
if (origin.reported)
|
|
4316
|
+
return;
|
|
4317
|
+
this.trace.writeStderr(faultDiagnostic(origin, index !== 0), null);
|
|
4318
|
+
origin.reported = true;
|
|
4319
|
+
});
|
|
4320
|
+
}
|
|
3068
4321
|
haltWithFault(fault) {
|
|
3069
4322
|
const isPanic = fault.kind === "panic";
|
|
3070
4323
|
const status = isPanic ? "panic" : "trap";
|
|
@@ -3074,7 +4327,7 @@ class ExecutorV1 {
|
|
|
3074
4327
|
: (fault.message ?? trapMessage(fault.trap));
|
|
3075
4328
|
const [code, kind] = faultInfo(fault);
|
|
3076
4329
|
// Native writes the failure line to stderr before it exits; so does this.
|
|
3077
|
-
this.
|
|
4330
|
+
this.reportFault(fault);
|
|
3078
4331
|
// A failing run reports no exit code: native exits 1 for every fault and
|
|
3079
4332
|
// names the kind in its message, so the kind is what travels, in
|
|
3080
4333
|
// `runtime_failures`. The page turns it into an exit code of its own.
|
|
@@ -3114,6 +4367,12 @@ function combineFaults(primary, secondary) {
|
|
|
3114
4367
|
}
|
|
3115
4368
|
function faultInfo(fault) {
|
|
3116
4369
|
if (fault.kind === "panic") {
|
|
4370
|
+
if (fault.handlerFailed)
|
|
4371
|
+
return [213, "ActorUnhandledFailure"];
|
|
4372
|
+
if (fault.timerOverdue)
|
|
4373
|
+
return [214, "ActorTimerOverdue"];
|
|
4374
|
+
if (fault.actorSendFailed)
|
|
4375
|
+
return [206, "ActorSendFailed"];
|
|
3117
4376
|
if (fault.cancelled)
|
|
3118
4377
|
return [-1, "Cancelled"];
|
|
3119
4378
|
if (fault.deadline)
|
|
@@ -3128,11 +4387,18 @@ function faultInfo(fault) {
|
|
|
3128
4387
|
actor_refused: [-4, "Refused"],
|
|
3129
4388
|
}[fault.trap];
|
|
3130
4389
|
}
|
|
3131
|
-
function
|
|
4390
|
+
function faultDiagnostic(fault, secondary = false) {
|
|
3132
4391
|
const [code, reason] = faultInfo(fault);
|
|
3133
4392
|
const label = secondary ? "secondary failure" : "failure";
|
|
3134
|
-
const message = fault.message === undefined
|
|
3135
|
-
|
|
4393
|
+
const message = fault.message === undefined ||
|
|
4394
|
+
(fault.kind === "panic" &&
|
|
4395
|
+
(fault.cancelled || fault.deadline || fault.timerOverdue))
|
|
4396
|
+
? ""
|
|
4397
|
+
: `: ${fault.message}`;
|
|
4398
|
+
return `hew: ${label}: ${reason} (${code})${message}\n`;
|
|
4399
|
+
}
|
|
4400
|
+
function faultReport(fault, secondary = false) {
|
|
4401
|
+
return (faultDiagnostic(fault, secondary) +
|
|
3136
4402
|
(fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
|
|
3137
4403
|
}
|
|
3138
4404
|
// ── refs ────────────────────────────────────────────────────────────────────
|
|
@@ -3207,6 +4473,8 @@ function actorErrorRole(reason) {
|
|
|
3207
4473
|
return "ActorErrorRoutingFailed";
|
|
3208
4474
|
case "EncodeFailed":
|
|
3209
4475
|
return "ActorErrorEncodeFailed";
|
|
4476
|
+
case "MessageTooLarge":
|
|
4477
|
+
return "ActorErrorMessageTooLarge";
|
|
3210
4478
|
case "ConnectionDropped":
|
|
3211
4479
|
return "ActorErrorConnectionDropped";
|
|
3212
4480
|
case "Partition":
|
|
@@ -3217,6 +4485,8 @@ function actorErrorRole(reason) {
|
|
|
3217
4485
|
}
|
|
3218
4486
|
function sendErrorRole(reason) {
|
|
3219
4487
|
switch (reason) {
|
|
4488
|
+
case "MessageTooLarge":
|
|
4489
|
+
return "SendErrorMessageTooLarge";
|
|
3220
4490
|
case "Full":
|
|
3221
4491
|
return "SendErrorFull";
|
|
3222
4492
|
case "Closed":
|
|
@@ -3286,7 +4556,9 @@ function toComparable(value) {
|
|
|
3286
4556
|
case "vector":
|
|
3287
4557
|
return value.items.map(toComparable);
|
|
3288
4558
|
case "rc":
|
|
3289
|
-
return
|
|
4559
|
+
return "Rc";
|
|
4560
|
+
case "weak":
|
|
4561
|
+
return "Weak";
|
|
3290
4562
|
case "map":
|
|
3291
4563
|
return [...value.entries.values()].map((entry) => [
|
|
3292
4564
|
toComparable(entry.key),
|
|
@@ -3459,7 +4731,7 @@ function applyBinary(op, lhs, rhs, ty) {
|
|
|
3459
4731
|
};
|
|
3460
4732
|
}
|
|
3461
4733
|
if (lhs.kind === "string") {
|
|
3462
|
-
return
|
|
4734
|
+
return stringValue(lhs.value + asString(rhs));
|
|
3463
4735
|
}
|
|
3464
4736
|
return {
|
|
3465
4737
|
kind: "i64",
|
|
@@ -3525,7 +4797,7 @@ function applyCast(value, to) {
|
|
|
3525
4797
|
return { kind: "bool", value: truth(value) };
|
|
3526
4798
|
case "char":
|
|
3527
4799
|
case "string":
|
|
3528
|
-
return
|
|
4800
|
+
return stringValue(asString(value));
|
|
3529
4801
|
default:
|
|
3530
4802
|
break;
|
|
3531
4803
|
}
|