@hew-lang/sandbox-vm 0.6.2 → 0.6.4
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 +43 -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 +26 -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/types.d.ts +4 -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 +2 -2
- package/dist/interpreter/v1/exec.d.ts +2 -2
- package/dist/interpreter/v1/exec.js +439 -183
- 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 +75846 -0
- package/dist/interpreter/v1/package.d.ts +5 -680
- package/dist/interpreter/v1/package.js +0 -12
- package/dist/interpreter/v1/scheduler.d.ts +5 -1
- package/dist/interpreter/v1/scheduler.js +32 -3
- package/dist/interpreter/v1/shims.d.ts +250 -7
- package/dist/interpreter/v1/shims.js +429 -150
- 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 +190 -202
- package/dist/interpreter/v1/value-operations.d.ts +7 -2
- package/dist/interpreter/v1/value-operations.js +13 -6
- package/dist/interpreter/values.d.ts +4 -0
- package/dist/interpreter/values.js +6 -0
- 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
|
|
@@ -12,13 +13,14 @@ import { TraceBuilder, runtimeFailure } from "../trace.js";
|
|
|
12
13
|
import { UNIT, cloneValue, stringValue, toJsonValue, } from "../values.js";
|
|
13
14
|
import { runtimeTag } from "./package.js";
|
|
14
15
|
import { StructuralRenderer } from "./structural.js";
|
|
16
|
+
import { timestampBinary } from "./timestamp.js";
|
|
15
17
|
import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
|
|
16
18
|
import { Pipes } from "./pipes.js";
|
|
17
19
|
import { FrameScheduler } from "./scheduler.js";
|
|
18
20
|
import { DeterministicIds } from "../../scheduler/ids.js";
|
|
19
21
|
import { Mt19937 } from "./mt19937.js";
|
|
20
|
-
import { ShimFault, decodeUtf8Checked,
|
|
21
|
-
import {
|
|
22
|
+
import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
|
|
23
|
+
import { AdmittedPackage } from "./validate.js";
|
|
22
24
|
const DEFAULT_STEP_BUDGET = 1_000_000;
|
|
23
25
|
const DEFAULT_ACTOR_STOP_NS = 5_000_000_000;
|
|
24
26
|
/// Ends the run. The status is already recorded on the trace.
|
|
@@ -55,7 +57,7 @@ class StdinReader {
|
|
|
55
57
|
return this.bytes.slice(start, this.offset);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
export function runPackageV1(pkg, options = {}) {
|
|
60
|
+
export async function runPackageV1(pkg, options = {}) {
|
|
59
61
|
const fixtureId = options.fixtureId ?? "package";
|
|
60
62
|
const replay = {
|
|
61
63
|
seed: options.replay?.seed ?? 0,
|
|
@@ -68,14 +70,25 @@ export function runPackageV1(pkg, options = {}) {
|
|
|
68
70
|
const trace = new TraceBuilder(pkg, fixtureId, options.traceId ?? `trace:${fixtureId}`, replay, options.sandboxVersion ?? "0.0.0-spec");
|
|
69
71
|
// Admission is a load-time decision: nothing runs when the package names a
|
|
70
72
|
// capability the VM has no shim for.
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
trace.reject(
|
|
73
|
+
const admission = AdmittedPackage.load(pkg, options);
|
|
74
|
+
if (!(admission instanceof AdmittedPackage)) {
|
|
75
|
+
trace.reject(admission);
|
|
74
76
|
return trace.finish();
|
|
75
77
|
}
|
|
76
|
-
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);
|
|
77
90
|
try {
|
|
78
|
-
vm.run();
|
|
91
|
+
await vm.run();
|
|
79
92
|
}
|
|
80
93
|
catch (error) {
|
|
81
94
|
if (!(error instanceof Halt)) {
|
|
@@ -86,7 +99,6 @@ export function runPackageV1(pkg, options = {}) {
|
|
|
86
99
|
return trace.finish();
|
|
87
100
|
}
|
|
88
101
|
class ExecutorV1 {
|
|
89
|
-
pkg;
|
|
90
102
|
trace;
|
|
91
103
|
stdin;
|
|
92
104
|
options;
|
|
@@ -111,16 +123,39 @@ class ExecutorV1 {
|
|
|
111
123
|
consumedResources = new WeakSet();
|
|
112
124
|
running = false;
|
|
113
125
|
rootComplete = false;
|
|
126
|
+
terminating = false;
|
|
114
127
|
rootFault = null;
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
pkg;
|
|
129
|
+
directories;
|
|
130
|
+
constructor(admission, trace, stdin, policy, options) {
|
|
117
131
|
this.trace = trace;
|
|
118
132
|
this.stdin = stdin;
|
|
119
133
|
this.options = options;
|
|
134
|
+
const pkg = admission.bytecode;
|
|
135
|
+
this.pkg = pkg;
|
|
136
|
+
this.directories = new BrowserDirectories(options.directoryRoots);
|
|
120
137
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
121
138
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
122
139
|
this.host = {
|
|
123
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
|
+
},
|
|
124
159
|
pipes: this.pipes,
|
|
125
160
|
generatorStream: (value) => {
|
|
126
161
|
const id = this.ids.channel();
|
|
@@ -150,7 +185,6 @@ class ExecutorV1 {
|
|
|
150
185
|
: this.pipes.cloneSink(value);
|
|
151
186
|
},
|
|
152
187
|
newPipe: (capacity) => this.newPipe(capacity),
|
|
153
|
-
closePipe: (value) => this.pipes.close(value, this.pipeFault(this.current)),
|
|
154
188
|
releaseValue: (value) => this.closeValue(value, this.pipeFault(this.current)),
|
|
155
189
|
writeStdout: (text) => this.trace.writeStdout(text, null),
|
|
156
190
|
writeStderr: (text) => this.trace.writeStderr(text, null),
|
|
@@ -183,24 +217,47 @@ class ExecutorV1 {
|
|
|
183
217
|
}
|
|
184
218
|
/// One instruction or terminator executed is one step, ownership ops
|
|
185
219
|
/// included: the package's ops are what the program does.
|
|
186
|
-
run() {
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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);
|
|
191
260
|
}
|
|
192
|
-
// Native main return drains accepted work before closing idle actors.
|
|
193
|
-
for (const owner of this.supervisors.values())
|
|
194
|
-
if (!owner.supervisor)
|
|
195
|
-
this.stopSupervisor(owner);
|
|
196
|
-
for (const actor of this.actors.values())
|
|
197
|
-
if (!actor.supervisor && actor.alive)
|
|
198
|
-
this.requestActorStop(actor);
|
|
199
|
-
this.scheduler.run();
|
|
200
|
-
if (this.rootFault)
|
|
201
|
-
this.haltWithFault(this.rootFault);
|
|
202
|
-
if (this.trace.exitCode === 0 && this.faultDebts.size)
|
|
203
|
-
this.trace.exitCode = 1;
|
|
204
261
|
}
|
|
205
262
|
runFrame(frame) {
|
|
206
263
|
this.current = frame;
|
|
@@ -241,7 +298,7 @@ class ExecutorV1 {
|
|
|
241
298
|
}
|
|
242
299
|
// ── frames ───────────────────────────────────────────────────────────────
|
|
243
300
|
activate(fn, args, caller, result, normal, unwind, context) {
|
|
244
|
-
const mandatoryClose = (this.pkg.resources ?? []).some((resource) => resource.close === fn.id);
|
|
301
|
+
const mandatoryClose = (this.pkg.resources ?? []).some((resource) => "close" in resource && resource.close === fn.id);
|
|
245
302
|
const parentContext = caller?.context ?? context;
|
|
246
303
|
parentContext.pollBudget ??= { remaining: 0 };
|
|
247
304
|
if (mandatoryClose && args[0])
|
|
@@ -268,7 +325,7 @@ class ExecutorV1 {
|
|
|
268
325
|
for (const block of fn.blocks) {
|
|
269
326
|
block.params.forEach(record);
|
|
270
327
|
for (const op of block.ops) {
|
|
271
|
-
if (op.dst != null && op.own === "none")
|
|
328
|
+
if ("dst" in op && op.dst != null && op.own === "none")
|
|
272
329
|
trivial.add(op.dst);
|
|
273
330
|
}
|
|
274
331
|
const term = block.term;
|
|
@@ -297,7 +354,7 @@ class ExecutorV1 {
|
|
|
297
354
|
parked: [],
|
|
298
355
|
caller,
|
|
299
356
|
result,
|
|
300
|
-
normal,
|
|
357
|
+
normal: normal ?? undefined,
|
|
301
358
|
unwind,
|
|
302
359
|
};
|
|
303
360
|
}
|
|
@@ -510,7 +567,7 @@ class ExecutorV1 {
|
|
|
510
567
|
case "aggregate.make":
|
|
511
568
|
this.define(act, op.dst, {
|
|
512
569
|
kind: "record",
|
|
513
|
-
typeId: this.aggregateName(op.shape),
|
|
570
|
+
typeId: op.shape === null ? "" : this.aggregateName(op.shape),
|
|
514
571
|
shape: op.shape ?? undefined,
|
|
515
572
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
516
573
|
});
|
|
@@ -977,8 +1034,12 @@ class ExecutorV1 {
|
|
|
977
1034
|
this.define(act, op.dst, { kind: "task", id: task.id });
|
|
978
1035
|
return;
|
|
979
1036
|
}
|
|
980
|
-
|
|
981
|
-
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
|
+
}
|
|
982
1043
|
}
|
|
983
1044
|
}
|
|
984
1045
|
/// Hand each field of a consumed container, or each payload of a matched
|
|
@@ -1028,6 +1089,20 @@ class ExecutorV1 {
|
|
|
1028
1089
|
case "return":
|
|
1029
1090
|
this.returnFrom(act, term.value ? this.boundary(act, term.value) : UNIT);
|
|
1030
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
|
+
}
|
|
1031
1106
|
case "checked.binary": {
|
|
1032
1107
|
const failure = checkedFailure(term.binary_op, this.read(act, term.lhs), this.read(act, term.rhs), term.ty);
|
|
1033
1108
|
if (failure) {
|
|
@@ -1080,132 +1155,128 @@ class ExecutorV1 {
|
|
|
1080
1155
|
return;
|
|
1081
1156
|
case "runtime.call": {
|
|
1082
1157
|
const entry = this.pkg.runtime_families[term.family];
|
|
1083
|
-
if (entry
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const tag = runtimeTag(shape, "CallerActor");
|
|
1089
|
-
payload.push({
|
|
1090
|
-
kind: "record",
|
|
1091
|
-
typeId: shape.cases[tag].fields[0],
|
|
1092
|
-
fields: [{ kind: "i64", value: caller.identity }],
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
this.completeShim(act, term, this.variant(term.result_shape, caller.kind === "Actor"
|
|
1096
|
-
? "CallerActor"
|
|
1097
|
-
: caller.kind === "Timer"
|
|
1098
|
-
? "CallerTimer"
|
|
1099
|
-
: "CallerTask", payload));
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
if (entry?.family === "ActorRefusalLog") {
|
|
1103
|
-
const [message, handler, reason] = term.args.map((argument) => this.boundary(act, argument));
|
|
1104
|
-
this.trace.snapshot("actor.delivery_refused", {
|
|
1105
|
-
actor_id: act.context.actor?.id ?? null,
|
|
1106
|
-
handler: toJsonValue(handler),
|
|
1107
|
-
message: toJsonValue(message),
|
|
1108
|
-
reason: toJsonValue(reason),
|
|
1109
|
-
});
|
|
1110
|
-
if (!act.context.reply)
|
|
1111
|
-
this.trace.writeStderr(`WARN actor.delivery_refused actor=${act.context.actor?.identity ?? 0n} handler=${asString(handler)} reason=${JSON.stringify(asString(reason))}\n`, null);
|
|
1112
|
-
this.completeShim(act, term, UNIT);
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
if (entry &&
|
|
1116
|
-
(entry.family === "Map" ||
|
|
1117
|
-
entry.family === "Set" ||
|
|
1118
|
-
(entry.family === "Array" && entry.detail === "Set") ||
|
|
1119
|
-
(entry.family === "Vector" &&
|
|
1120
|
-
["Contains", "Clear", "Set"].includes(String(entry.detail))))) {
|
|
1121
|
-
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1122
|
-
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);
|
|
1123
|
-
this.runValueProgram(act, term, args, iterator);
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
if (entry &&
|
|
1127
|
-
[
|
|
1128
|
-
"RcNew",
|
|
1129
|
-
"RcClone",
|
|
1130
|
-
"RcDowngrade",
|
|
1131
|
-
"WeakCloneRc",
|
|
1132
|
-
"WeakUpgradeRc",
|
|
1133
|
-
"RcGet",
|
|
1134
|
-
"RcIsUnique",
|
|
1135
|
-
"RcSet",
|
|
1136
|
-
"RcStrongCount",
|
|
1137
|
-
"RcWeakCount",
|
|
1138
|
-
].includes(entry.family)) {
|
|
1139
|
-
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1140
|
-
this.runValueProgram(act, term, args, sharedOperation(entry.family, args, act.rcOwner, term.shared_payload_type ?? "", (value) => this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : [])));
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
if (entry?.family === "SupervisorPool") {
|
|
1144
|
-
this.supervisorPool(act, term, String(entry.detail));
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
if (entry && ["SinkClose", "StreamClose"].includes(entry.family)) {
|
|
1148
|
-
const value = this.boundary(act, term.args[0]);
|
|
1149
|
-
this.running = false;
|
|
1150
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
1151
|
-
if (fault)
|
|
1152
|
-
this.raiseFault(act, fault, term.unwind);
|
|
1153
|
-
else
|
|
1154
|
-
this.completeShim(act, term, UNIT);
|
|
1155
|
-
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1156
|
-
}, act.context.actor, act.rcOwner);
|
|
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);
|
|
1157
1163
|
return;
|
|
1158
1164
|
}
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
]);
|
|
1170
|
-
else {
|
|
1171
|
-
const shapes = term.variant_result_shapes;
|
|
1172
|
-
const error = this.pkg.aggregates[shapes.error];
|
|
1173
|
-
result = this.variant(term.result_shape, "ResultErr", [
|
|
1174
|
-
{
|
|
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
1175
|
kind: "record",
|
|
1176
|
-
typeId:
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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)}`);
|
|
1186
1261
|
}
|
|
1187
|
-
this.completeShim(act, term, result);
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
const shim = entry ? resolveRuntimeShim(entry) : undefined;
|
|
1191
|
-
if (!entry || !shim) {
|
|
1192
|
-
throw new Error(`runtime family ${term.family} resolved no shim after admission`);
|
|
1193
1262
|
}
|
|
1194
|
-
this.callShim(act, term, shim);
|
|
1195
|
-
return;
|
|
1196
1263
|
}
|
|
1197
1264
|
case "extern.call": {
|
|
1198
1265
|
const entry = this.pkg.externs[term.extern];
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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);
|
|
1204
1275
|
return;
|
|
1205
1276
|
}
|
|
1206
1277
|
case "wire.codec": {
|
|
1207
1278
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1208
|
-
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 ?? []));
|
|
1209
1280
|
return;
|
|
1210
1281
|
}
|
|
1211
1282
|
case "value.call": {
|
|
@@ -1316,6 +1387,29 @@ class ExecutorV1 {
|
|
|
1316
1387
|
act.block = target;
|
|
1317
1388
|
act.opIndex = 0;
|
|
1318
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
|
+
}
|
|
1319
1413
|
/// Run a runtime or extern shim. A fault it raises takes the call's `unwind`
|
|
1320
1414
|
/// edge so the cleanup the package names runs before the program ends.
|
|
1321
1415
|
callShim(act, term, shim) {
|
|
@@ -1329,7 +1423,9 @@ class ExecutorV1 {
|
|
|
1329
1423
|
: (this.pkg.variants[shapeId] ?? null);
|
|
1330
1424
|
let value;
|
|
1331
1425
|
try {
|
|
1332
|
-
|
|
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);
|
|
1333
1429
|
}
|
|
1334
1430
|
catch (error) {
|
|
1335
1431
|
if (error instanceof ShimFault) {
|
|
@@ -1441,11 +1537,19 @@ class ExecutorV1 {
|
|
|
1441
1537
|
this.completeShim(act, term, this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : []));
|
|
1442
1538
|
return;
|
|
1443
1539
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
+
}
|
|
1447
1552
|
}
|
|
1448
|
-
this.completeShim(act, term, value);
|
|
1449
1553
|
}
|
|
1450
1554
|
structuralFormat(act, term) {
|
|
1451
1555
|
if (term.structural == null)
|
|
@@ -1655,7 +1759,11 @@ class ExecutorV1 {
|
|
|
1655
1759
|
this.current = caller;
|
|
1656
1760
|
this.takeEdge(caller, act.unwind);
|
|
1657
1761
|
}
|
|
1658
|
-
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;
|
|
1659
1767
|
switch (term.kind) {
|
|
1660
1768
|
case "Yield": {
|
|
1661
1769
|
const generator = act.context.generator;
|
|
@@ -1868,6 +1976,35 @@ class ExecutorV1 {
|
|
|
1868
1976
|
return;
|
|
1869
1977
|
}
|
|
1870
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
|
+
}
|
|
1871
2008
|
if (term.detail?.operation === "SinkFinish") {
|
|
1872
2009
|
const wake = this.park(act, term);
|
|
1873
2010
|
const cancel = act.context.cancel;
|
|
@@ -1887,7 +2024,11 @@ class ExecutorV1 {
|
|
|
1887
2024
|
});
|
|
1888
2025
|
return;
|
|
1889
2026
|
}
|
|
1890
|
-
|
|
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,
|
|
1891
2032
|
// so the records concatenate to the stdin the program consumed.
|
|
1892
2033
|
const line = this.stdin.readLine() ?? new Uint8Array();
|
|
1893
2034
|
this.trace.recordReplayInput({ kind: "stdin", data: new TextDecoder().decode(line) }, false);
|
|
@@ -1938,8 +2079,10 @@ class ExecutorV1 {
|
|
|
1938
2079
|
cancelTimer = this.scheduler.after(nanos, () => wake(UNIT));
|
|
1939
2080
|
return;
|
|
1940
2081
|
}
|
|
1941
|
-
default:
|
|
1942
|
-
|
|
2082
|
+
default: {
|
|
2083
|
+
const exhaustive = term;
|
|
2084
|
+
throw new Error(`unknown suspension handler ${String(exhaustive)}`);
|
|
2085
|
+
}
|
|
1943
2086
|
}
|
|
1944
2087
|
}
|
|
1945
2088
|
pendingActorWait(act, targets, externalProgress, operation, fail) {
|
|
@@ -2323,12 +2466,18 @@ class ExecutorV1 {
|
|
|
2323
2466
|
value.cell.weak -= 1;
|
|
2324
2467
|
}
|
|
2325
2468
|
break;
|
|
2469
|
+
case "host_resource": {
|
|
2470
|
+
if (value.owner.closed)
|
|
2471
|
+
break;
|
|
2472
|
+
this.closeHostResource(value);
|
|
2473
|
+
break;
|
|
2474
|
+
}
|
|
2326
2475
|
case "timer": {
|
|
2327
2476
|
if (value.closed)
|
|
2328
2477
|
break;
|
|
2329
2478
|
const resource = this.pkg.resources?.[value.resource];
|
|
2330
|
-
if (resource?.
|
|
2331
|
-
throw new Error("
|
|
2479
|
+
if (resource?.kind !== "opaque")
|
|
2480
|
+
throw new Error("opaque owner lost its selected close");
|
|
2332
2481
|
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
2333
2482
|
return;
|
|
2334
2483
|
}
|
|
@@ -2363,7 +2512,7 @@ class ExecutorV1 {
|
|
|
2363
2512
|
break;
|
|
2364
2513
|
case "record": {
|
|
2365
2514
|
const resource = (this.pkg.resources ?? []).find((resource) => resource.kind === "record" && resource.shape === value.shape);
|
|
2366
|
-
if (resource?.
|
|
2515
|
+
if (resource?.kind === "record") {
|
|
2367
2516
|
if (this.consumedResources.has(value))
|
|
2368
2517
|
break;
|
|
2369
2518
|
if (!this.closingResources.has(value)) {
|
|
@@ -2417,7 +2566,20 @@ class ExecutorV1 {
|
|
|
2417
2566
|
};
|
|
2418
2567
|
next();
|
|
2419
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
|
+
}
|
|
2420
2580
|
closeValue(value, fault) {
|
|
2581
|
+
if (value.kind === "host_resource")
|
|
2582
|
+
this.closeHostResource(value);
|
|
2421
2583
|
if (value.kind === "string")
|
|
2422
2584
|
value.release();
|
|
2423
2585
|
if (value.kind === "closure")
|
|
@@ -2587,7 +2749,19 @@ class ExecutorV1 {
|
|
|
2587
2749
|
this.trace.allocateId("actor", actor.id, act.context.id);
|
|
2588
2750
|
this.trace.snapshot("actor.spawn", { actor_id: actor.id });
|
|
2589
2751
|
const handle = { kind: "actor", id: actor.id };
|
|
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
|
+
};
|
|
2590
2763
|
const failed = (fault) => {
|
|
2764
|
+
act.context.cancel = undefined;
|
|
2591
2765
|
actor.active = null;
|
|
2592
2766
|
actor.alive = false;
|
|
2593
2767
|
actor.constructing = false;
|
|
@@ -2618,6 +2792,7 @@ class ExecutorV1 {
|
|
|
2618
2792
|
}
|
|
2619
2793
|
actor.constructing = false;
|
|
2620
2794
|
actor.active = null;
|
|
2795
|
+
act.context.cancel = undefined;
|
|
2621
2796
|
act.rcOwner = null;
|
|
2622
2797
|
actor.busy = false;
|
|
2623
2798
|
this.startPeriodicTimers(actor);
|
|
@@ -2629,12 +2804,12 @@ class ExecutorV1 {
|
|
|
2629
2804
|
// Construction enters the same interpreter loop as its caller. Only
|
|
2630
2805
|
// a real suspension queues work and advances deterministic scheduling.
|
|
2631
2806
|
const start = () => {
|
|
2632
|
-
if (layout.start
|
|
2807
|
+
if (layout.start != null)
|
|
2633
2808
|
actor.active = this.invokeFrame(actor, layout.start, [actor.state], started, failed, actor.rcOwner, true);
|
|
2634
2809
|
else
|
|
2635
2810
|
started();
|
|
2636
2811
|
};
|
|
2637
|
-
if (layout.init
|
|
2812
|
+
if (layout.init != null)
|
|
2638
2813
|
actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
|
|
2639
2814
|
else
|
|
2640
2815
|
start();
|
|
@@ -2771,7 +2946,7 @@ class ExecutorV1 {
|
|
|
2771
2946
|
this.raiseFault(act, { kind: "panic", message: "stream producer is closed" }, term.unwind);
|
|
2772
2947
|
}
|
|
2773
2948
|
else {
|
|
2774
|
-
if (actor.layout.mailbox_capacity
|
|
2949
|
+
if (actor.layout.mailbox_capacity != null &&
|
|
2775
2950
|
actor.mailbox.length >= actor.layout.mailbox_capacity) {
|
|
2776
2951
|
actor.admission.push(admit);
|
|
2777
2952
|
return;
|
|
@@ -2946,7 +3121,8 @@ class ExecutorV1 {
|
|
|
2946
3121
|
specs: layout.children.flatMap((child) => Array.from({ length: members }, () => child)),
|
|
2947
3122
|
offsets: layout.children.map((_child, index) => index),
|
|
2948
3123
|
recovering: new Set(),
|
|
2949
|
-
restartBusy:
|
|
3124
|
+
restartBusy: true,
|
|
3125
|
+
constructor: null,
|
|
2950
3126
|
pendingRestarts: [],
|
|
2951
3127
|
spent: new Set(),
|
|
2952
3128
|
completed: false,
|
|
@@ -2961,20 +3137,39 @@ class ExecutorV1 {
|
|
|
2961
3137
|
this.trace.allocateId("supervisor", owner.id, act.context.id);
|
|
2962
3138
|
this.trace.snapshot("supervisor.spawn", { supervisor_id: owner.id });
|
|
2963
3139
|
this.running = false;
|
|
2964
|
-
|
|
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);
|
|
2965
3160
|
const next = () => {
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
this.raiseFault(act, fault, term.unwind);
|
|
2970
|
-
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2971
|
-
});
|
|
2972
|
-
this.stopSupervisor(owner);
|
|
2973
|
-
});
|
|
3161
|
+
const child = order.shift();
|
|
3162
|
+
if (child !== undefined) {
|
|
3163
|
+
this.startChild(owner, child, next, fail);
|
|
2974
3164
|
}
|
|
2975
3165
|
else {
|
|
3166
|
+
act.context.cancel = undefined;
|
|
3167
|
+
owner.restartBusy = false;
|
|
2976
3168
|
this.completeShim(act, term, { kind: "supervisor", id: owner.id });
|
|
2977
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);
|
|
2978
3173
|
}
|
|
2979
3174
|
};
|
|
2980
3175
|
next();
|
|
@@ -3107,8 +3302,12 @@ class ExecutorV1 {
|
|
|
3107
3302
|
id: act.context.actor.id,
|
|
3108
3303
|
});
|
|
3109
3304
|
return;
|
|
3110
|
-
|
|
3111
|
-
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
|
+
}
|
|
3112
3311
|
}
|
|
3113
3312
|
}
|
|
3114
3313
|
scopeDeadline(act, expected) {
|
|
@@ -3216,7 +3415,7 @@ class ExecutorV1 {
|
|
|
3216
3415
|
selectedTarget?.(actor);
|
|
3217
3416
|
if (settled || closing)
|
|
3218
3417
|
return;
|
|
3219
|
-
const full = actor.layout.mailbox_capacity
|
|
3418
|
+
const full = actor.layout.mailbox_capacity != null &&
|
|
3220
3419
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3221
3420
|
if (full) {
|
|
3222
3421
|
if (protocol.policy === "reject") {
|
|
@@ -3308,7 +3507,7 @@ class ExecutorV1 {
|
|
|
3308
3507
|
admitActorMessage(actor, message) {
|
|
3309
3508
|
if (!actor.alive || actor.closing)
|
|
3310
3509
|
return "closed";
|
|
3311
|
-
const full = actor.layout.mailbox_capacity
|
|
3510
|
+
const full = actor.layout.mailbox_capacity != null &&
|
|
3312
3511
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3313
3512
|
let declared = full ? actor.layout.overflow : null;
|
|
3314
3513
|
const discard = (lost) => {
|
|
@@ -3608,7 +3807,7 @@ class ExecutorV1 {
|
|
|
3608
3807
|
}, actor);
|
|
3609
3808
|
};
|
|
3610
3809
|
const runHook = () => {
|
|
3611
|
-
if (actor.layout.crash
|
|
3810
|
+
if (actor.layout.crash != null) {
|
|
3612
3811
|
const [code, message] = faultInfo(fault);
|
|
3613
3812
|
const info = {
|
|
3614
3813
|
kind: "record",
|
|
@@ -3689,9 +3888,42 @@ class ExecutorV1 {
|
|
|
3689
3888
|
throw new Error("supervisor does not exist");
|
|
3690
3889
|
return owner;
|
|
3691
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
|
+
}
|
|
3692
3900
|
startChild(owner, child, done, failed) {
|
|
3693
3901
|
const spec = owner.specs[child];
|
|
3694
|
-
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
|
+
}
|
|
3695
3927
|
owner.children[child] = value;
|
|
3696
3928
|
if (value.kind === "actor")
|
|
3697
3929
|
this.actorFor(value).supervisor = { owner, child };
|
|
@@ -3704,7 +3936,15 @@ class ExecutorV1 {
|
|
|
3704
3936
|
child,
|
|
3705
3937
|
});
|
|
3706
3938
|
done();
|
|
3707
|
-
},
|
|
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
|
+
});
|
|
3708
3948
|
}
|
|
3709
3949
|
roleOwner(role) {
|
|
3710
3950
|
const parentRole = this.roles.get(role.ownerId);
|
|
@@ -3799,7 +4039,7 @@ class ExecutorV1 {
|
|
|
3799
4039
|
stop();
|
|
3800
4040
|
});
|
|
3801
4041
|
};
|
|
3802
|
-
const starting =
|
|
4042
|
+
const starting = this.supervisorStartupOrder(owner).filter((index) => affected.includes(index));
|
|
3803
4043
|
const start = () => {
|
|
3804
4044
|
if (!owner.alive) {
|
|
3805
4045
|
owner.restartBusy = false;
|
|
@@ -3880,7 +4120,7 @@ class ExecutorV1 {
|
|
|
3880
4120
|
}
|
|
3881
4121
|
childStopBudget(owner, child) {
|
|
3882
4122
|
const spec = owner.specs[child];
|
|
3883
|
-
if (spec.stop_config
|
|
4123
|
+
if (spec.stop_config != null) {
|
|
3884
4124
|
const duration = owner.config[spec.stop_config];
|
|
3885
4125
|
if (duration?.kind !== "i64" || duration.value < 0n)
|
|
3886
4126
|
throw new Error("child stop budget is not a non-negative duration");
|
|
@@ -3898,6 +4138,18 @@ class ExecutorV1 {
|
|
|
3898
4138
|
if (!owner.alive)
|
|
3899
4139
|
return;
|
|
3900
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) {
|
|
3901
4153
|
let childIndex = owner.children.length - 1;
|
|
3902
4154
|
const next = () => {
|
|
3903
4155
|
owner.teardownCancelTimer?.();
|
|
@@ -4221,6 +4473,8 @@ function actorErrorRole(reason) {
|
|
|
4221
4473
|
return "ActorErrorRoutingFailed";
|
|
4222
4474
|
case "EncodeFailed":
|
|
4223
4475
|
return "ActorErrorEncodeFailed";
|
|
4476
|
+
case "MessageTooLarge":
|
|
4477
|
+
return "ActorErrorMessageTooLarge";
|
|
4224
4478
|
case "ConnectionDropped":
|
|
4225
4479
|
return "ActorErrorConnectionDropped";
|
|
4226
4480
|
case "Partition":
|
|
@@ -4231,6 +4485,8 @@ function actorErrorRole(reason) {
|
|
|
4231
4485
|
}
|
|
4232
4486
|
function sendErrorRole(reason) {
|
|
4233
4487
|
switch (reason) {
|
|
4488
|
+
case "MessageTooLarge":
|
|
4489
|
+
return "SendErrorMessageTooLarge";
|
|
4234
4490
|
case "Full":
|
|
4235
4491
|
return "SendErrorFull";
|
|
4236
4492
|
case "Closed":
|