@hew-lang/sandbox-vm 0.6.1 → 0.6.2
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 +5 -0
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +2 -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/codecs.js +6 -9
- package/dist/interpreter/v1/exec.js +1336 -320
- package/dist/interpreter/v1/package.d.ts +39 -4
- package/dist/interpreter/v1/pipes.d.ts +1 -0
- package/dist/interpreter/v1/pipes.js +9 -6
- package/dist/interpreter/v1/scheduler.d.ts +3 -0
- package/dist/interpreter/v1/scheduler.js +18 -8
- package/dist/interpreter/v1/shims.d.ts +10 -0
- package/dist/interpreter/v1/shims.js +226 -34
- package/dist/interpreter/v1/validate.js +103 -7
- package/dist/interpreter/v1/value-operations.d.ts +5 -2
- package/dist/interpreter/v1/value-operations.js +57 -12
- package/dist/interpreter/values.d.ts +26 -8
- package/dist/interpreter/values.js +25 -5
- package/package.json +4 -4
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
/// follows the `unwind` edge the package names, and takes the failure edge a
|
|
7
7
|
/// `checked.binary` names rather than trapping on its own.
|
|
8
8
|
import { executeCodec } from "./codecs.js";
|
|
9
|
+
import { actorIdentical } from "./actor-identity.js";
|
|
10
|
+
import { ActorWaits } from "./actor-waits.js";
|
|
9
11
|
import { TraceBuilder, runtimeFailure } from "../trace.js";
|
|
10
|
-
import { UNIT, cloneValue, toJsonValue } from "../values.js";
|
|
12
|
+
import { UNIT, cloneValue, stringValue, toJsonValue, } from "../values.js";
|
|
11
13
|
import { runtimeTag } from "./package.js";
|
|
12
14
|
import { StructuralRenderer } from "./structural.js";
|
|
13
15
|
import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
|
|
@@ -15,9 +17,10 @@ import { Pipes } from "./pipes.js";
|
|
|
15
17
|
import { FrameScheduler } from "./scheduler.js";
|
|
16
18
|
import { DeterministicIds } from "../../scheduler/ids.js";
|
|
17
19
|
import { Mt19937 } from "./mt19937.js";
|
|
18
|
-
import { ShimFault, resolveExternShim, resolveRuntimeShim, } from "./shims.js";
|
|
20
|
+
import { ShimFault, decodeUtf8Checked, resolveExternShim, resolveRuntimeShim, } from "./shims.js";
|
|
19
21
|
import { admitPackage } from "./validate.js";
|
|
20
22
|
const DEFAULT_STEP_BUDGET = 1_000_000;
|
|
23
|
+
const DEFAULT_ACTOR_STOP_NS = 5_000_000_000;
|
|
21
24
|
/// Ends the run. The status is already recorded on the trace.
|
|
22
25
|
class Halt extends Error {
|
|
23
26
|
status;
|
|
@@ -92,10 +95,13 @@ class ExecutorV1 {
|
|
|
92
95
|
scheduler;
|
|
93
96
|
ids;
|
|
94
97
|
actors = new Map();
|
|
98
|
+
actorWaits = new ActorWaits();
|
|
95
99
|
supervisors = new Map();
|
|
96
100
|
roles = new Map();
|
|
97
101
|
pipes = new Pipes();
|
|
98
102
|
generators = new Map();
|
|
103
|
+
generatorStreams = new Map();
|
|
104
|
+
senderEndpoints = new Map();
|
|
99
105
|
tasks = new Map();
|
|
100
106
|
completedTasks = 0;
|
|
101
107
|
actorIdentity = 0n;
|
|
@@ -114,7 +120,35 @@ class ExecutorV1 {
|
|
|
114
120
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
115
121
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
116
122
|
this.host = {
|
|
123
|
+
argv: [...(options.argv ?? ["main.hew"])],
|
|
117
124
|
pipes: this.pipes,
|
|
125
|
+
generatorStream: (value) => {
|
|
126
|
+
const id = this.ids.channel();
|
|
127
|
+
this.generatorStreams.set(id, {
|
|
128
|
+
generator: this.generatorFor(value),
|
|
129
|
+
closed: false,
|
|
130
|
+
pending: null,
|
|
131
|
+
fault: null,
|
|
132
|
+
receiving: false,
|
|
133
|
+
});
|
|
134
|
+
return { kind: "stream", channelId: id };
|
|
135
|
+
},
|
|
136
|
+
encoderSink: (sink, encoder) => this.senderEndpoint({
|
|
137
|
+
sink,
|
|
138
|
+
encoder,
|
|
139
|
+
references: 0,
|
|
140
|
+
endpoints: 0,
|
|
141
|
+
busy: false,
|
|
142
|
+
waiting: new Set(),
|
|
143
|
+
}, !this.sinkClosed(sink)),
|
|
144
|
+
cloneSink: (value) => {
|
|
145
|
+
const endpoint = value.kind === "sink"
|
|
146
|
+
? this.senderEndpoints.get(value.channelId)
|
|
147
|
+
: undefined;
|
|
148
|
+
return endpoint
|
|
149
|
+
? this.senderEndpoint(endpoint.owner, endpoint.open)
|
|
150
|
+
: this.pipes.cloneSink(value);
|
|
151
|
+
},
|
|
118
152
|
newPipe: (capacity) => this.newPipe(capacity),
|
|
119
153
|
closePipe: (value) => this.pipes.close(value, this.pipeFault(this.current)),
|
|
120
154
|
releaseValue: (value) => this.closeValue(value, this.pipeFault(this.current)),
|
|
@@ -135,7 +169,7 @@ class ExecutorV1 {
|
|
|
135
169
|
id: "actor:root",
|
|
136
170
|
actor: null,
|
|
137
171
|
returned: (value) => {
|
|
138
|
-
this.
|
|
172
|
+
this.completeRoot();
|
|
139
173
|
this.trace.exitCode =
|
|
140
174
|
pkg.entry?.exit === "status" && value.kind === "i64"
|
|
141
175
|
? Number(value.value)
|
|
@@ -143,7 +177,7 @@ class ExecutorV1 {
|
|
|
143
177
|
},
|
|
144
178
|
failed: (fault) => {
|
|
145
179
|
this.rootFault = fault;
|
|
146
|
-
this.
|
|
180
|
+
this.completeRoot();
|
|
147
181
|
},
|
|
148
182
|
});
|
|
149
183
|
}
|
|
@@ -173,6 +207,10 @@ class ExecutorV1 {
|
|
|
173
207
|
this.running = true;
|
|
174
208
|
while (this.running) {
|
|
175
209
|
const act = this.current;
|
|
210
|
+
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
211
|
+
if (this.scopeDeadline(act, fault) !== fault)
|
|
212
|
+
act.context.pendingCancellation ??= fault;
|
|
213
|
+
};
|
|
176
214
|
while (act.opIndex < act.block.ops.length) {
|
|
177
215
|
const op = act.block.ops[act.opIndex++];
|
|
178
216
|
if (op.op !== "coverage.hit")
|
|
@@ -203,9 +241,19 @@ class ExecutorV1 {
|
|
|
203
241
|
}
|
|
204
242
|
// ── frames ───────────────────────────────────────────────────────────────
|
|
205
243
|
activate(fn, args, caller, result, normal, unwind, context) {
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
const mandatoryClose = (this.pkg.resources ?? []).some((resource) => resource.close === fn.id);
|
|
245
|
+
const parentContext = caller?.context ?? context;
|
|
246
|
+
parentContext.pollBudget ??= { remaining: 0 };
|
|
247
|
+
if (mandatoryClose && args[0])
|
|
208
248
|
this.closingResources.add(args[0]);
|
|
249
|
+
const executionContext = mandatoryClose
|
|
250
|
+
? {
|
|
251
|
+
...parentContext,
|
|
252
|
+
cancel: undefined,
|
|
253
|
+
pendingCancellation: undefined,
|
|
254
|
+
requestDeadline: undefined,
|
|
255
|
+
}
|
|
256
|
+
: parentContext;
|
|
209
257
|
const env = new Map();
|
|
210
258
|
for (const [at, param] of fn.params.entries()) {
|
|
211
259
|
env.set(param.value, ownedRef(args[at] ?? UNIT));
|
|
@@ -231,7 +279,13 @@ class ExecutorV1 {
|
|
|
231
279
|
arm.fields.forEach(record);
|
|
232
280
|
}
|
|
233
281
|
return {
|
|
234
|
-
|
|
282
|
+
rcOwner: fn.actor_construction
|
|
283
|
+
? { blocks: new Set(), closed: false }
|
|
284
|
+
: (caller?.rcOwner ??
|
|
285
|
+
executionContext.rcOwner ??
|
|
286
|
+
executionContext.actor?.rcOwner ??
|
|
287
|
+
null),
|
|
288
|
+
context: executionContext,
|
|
235
289
|
fn,
|
|
236
290
|
block: this.blockAt(fn, fn.entry),
|
|
237
291
|
opIndex: 0,
|
|
@@ -373,7 +427,7 @@ class ExecutorV1 {
|
|
|
373
427
|
this.define(act, op.dst, { kind: "f64", value: floatConst(op) });
|
|
374
428
|
return;
|
|
375
429
|
case "const.char":
|
|
376
|
-
this.define(act, op.dst,
|
|
430
|
+
this.define(act, op.dst, stringValue(op.value));
|
|
377
431
|
return;
|
|
378
432
|
case "const.unit":
|
|
379
433
|
this.define(act, op.dst, UNIT);
|
|
@@ -382,10 +436,7 @@ class ExecutorV1 {
|
|
|
382
436
|
this.define(act, op.dst, { kind: "i64", value: BigInt(op.nanos) });
|
|
383
437
|
return;
|
|
384
438
|
case "const.str":
|
|
385
|
-
this.define(act, op.dst,
|
|
386
|
-
kind: "string",
|
|
387
|
-
value: this.pkg.strings[op.str] ?? "",
|
|
388
|
-
});
|
|
439
|
+
this.define(act, op.dst, stringValue(this.pkg.strings[op.str] ?? ""));
|
|
389
440
|
return;
|
|
390
441
|
case "const.bytes":
|
|
391
442
|
this.define(act, op.dst, bytesValue(this.pkg.bytes[op.bytes] ?? []));
|
|
@@ -652,8 +703,9 @@ class ExecutorV1 {
|
|
|
652
703
|
const context = {
|
|
653
704
|
id,
|
|
654
705
|
actor: act.context.actor,
|
|
706
|
+
rcOwner: act.rcOwner,
|
|
655
707
|
requestDeadline: this.requestDeadline(act),
|
|
656
|
-
returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault)),
|
|
708
|
+
returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault), act.context.actor, act.rcOwner),
|
|
657
709
|
failed: (fault) => this.finishGenerator(generator, fault),
|
|
658
710
|
};
|
|
659
711
|
const generator = {
|
|
@@ -748,10 +800,10 @@ class ExecutorV1 {
|
|
|
748
800
|
changed();
|
|
749
801
|
};
|
|
750
802
|
if (failure && winner)
|
|
751
|
-
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor);
|
|
803
|
+
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor, act.rcOwner);
|
|
752
804
|
else
|
|
753
805
|
finish();
|
|
754
|
-
}, act.context.actor);
|
|
806
|
+
}, act.context.actor, act.rcOwner);
|
|
755
807
|
};
|
|
756
808
|
task.cancel = () => {
|
|
757
809
|
if (task.done || task.finishing)
|
|
@@ -770,12 +822,44 @@ class ExecutorV1 {
|
|
|
770
822
|
ready();
|
|
771
823
|
return;
|
|
772
824
|
}
|
|
773
|
-
case "task.spawn":
|
|
825
|
+
case "task.spawn":
|
|
826
|
+
case "task.invoke": {
|
|
774
827
|
const group = act.scopes.get(op.scope);
|
|
775
828
|
if (!group)
|
|
776
829
|
throw new Error(`task scope ${op.scope} is missing`);
|
|
777
|
-
const
|
|
778
|
-
|
|
830
|
+
const consuming = op.op === "task.spawn" ||
|
|
831
|
+
("receiver" in op.callee && op.callee.receiver.decision === "move");
|
|
832
|
+
const dynamic = op.op === "task.invoke" && "dyn" in op.callee ? op.callee : null;
|
|
833
|
+
const dynamicReceiver = dynamic
|
|
834
|
+
? this.boundary(act, dynamic.receiver)
|
|
835
|
+
: null;
|
|
836
|
+
if (dynamicReceiver && dynamicReceiver.kind !== "dyn")
|
|
837
|
+
throw new TypeError("prepared dynamic receiver is not a trait object");
|
|
838
|
+
const dynamicEntry = dynamicReceiver?.kind === "dyn" && dynamic
|
|
839
|
+
? this.dynamicEntry(dynamicReceiver, dynamic.dyn)
|
|
840
|
+
: null;
|
|
841
|
+
if (dynamic && dynamicEntry?.method !== dynamic.method)
|
|
842
|
+
throw new Error("prepared dynamic slot differs from its checked method");
|
|
843
|
+
const callable = op.op === "task.spawn"
|
|
844
|
+
? this.read(act, op.callable)
|
|
845
|
+
: "direct" in op.callee
|
|
846
|
+
? { kind: "function", id: String(op.callee.direct) }
|
|
847
|
+
: "mutable" in op.callee
|
|
848
|
+
? { kind: "function", id: String(op.callee.mutable) }
|
|
849
|
+
: "dyn" in op.callee
|
|
850
|
+
? {
|
|
851
|
+
kind: "function",
|
|
852
|
+
id: String(dynamicEntry.callee),
|
|
853
|
+
}
|
|
854
|
+
: this.boundary(act, op.callee.receiver);
|
|
855
|
+
if (op.op === "task.spawn")
|
|
856
|
+
this.invalidate(act, op.callable);
|
|
857
|
+
const args = op.op === "task.invoke"
|
|
858
|
+
? op.args.map((arg) => this.boundary(act, arg))
|
|
859
|
+
: [];
|
|
860
|
+
const specialization = op.op === "task.invoke" && "specialization" in op.callee
|
|
861
|
+
? op.callee.specialization
|
|
862
|
+
: null;
|
|
779
863
|
const task = this.newTask();
|
|
780
864
|
group.tasks.push(task);
|
|
781
865
|
const settle = (value, fault) => {
|
|
@@ -786,9 +870,7 @@ class ExecutorV1 {
|
|
|
786
870
|
!(fault.kind === "panic" && (fault.cancelled || fault.deadline)) &&
|
|
787
871
|
act.context.actor)
|
|
788
872
|
act.context.actor.crashing ??= fault;
|
|
789
|
-
|
|
790
|
-
? this.faultText(act.context.actor.crashing)
|
|
791
|
-
: null, (closeFault) => {
|
|
873
|
+
const complete = (closeFault) => {
|
|
792
874
|
if (!fault || (fault.kind === "panic" && fault.cancelled))
|
|
793
875
|
fault = closeFault ?? fault;
|
|
794
876
|
this.settleTask(task, value, fault);
|
|
@@ -803,33 +885,92 @@ class ExecutorV1 {
|
|
|
803
885
|
}
|
|
804
886
|
for (const changed of [...group.changed])
|
|
805
887
|
changed();
|
|
806
|
-
}
|
|
888
|
+
};
|
|
889
|
+
if (!consuming) {
|
|
890
|
+
complete(null);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
this.closeValueAsync(callable, act.context.actor
|
|
894
|
+
? this.faultText(act.context.actor.crashing)
|
|
895
|
+
: null, complete, act.context.actor, act.rcOwner);
|
|
896
|
+
};
|
|
897
|
+
const mutable = op.op === "task.invoke" && "mutable" in op.callee ? op.callee : null;
|
|
898
|
+
const receiverRef = mutable
|
|
899
|
+
? this.refOf(act, mutable.receiver.value)
|
|
900
|
+
: null;
|
|
901
|
+
if (dynamicReceiver?.kind === "dyn" && dynamicEntry && dynamic)
|
|
902
|
+
args.unshift(this.erasedReceiver(act, dynamic.receiver, dynamicReceiver.value, dynamicEntry.receiver));
|
|
903
|
+
if (receiverRef)
|
|
904
|
+
args.unshift(mutable.staged
|
|
905
|
+
? cloneValue(readRef(receiverRef))
|
|
906
|
+
: readRef(receiverRef));
|
|
907
|
+
const handback = (value, fault, result) => {
|
|
908
|
+
if (!receiverRef || !mutable || value === null)
|
|
909
|
+
throw new Error("prepared mutable method omitted its receiver handback");
|
|
910
|
+
if (mutable.staged && fault) {
|
|
911
|
+
this.closeValueAsync(value, this.faultText(fault), (closeFault) => settle(UNIT, combineFaults(fault, closeFault)), act.context.actor, act.rcOwner);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
const previous = readRef(receiverRef);
|
|
915
|
+
writeRef(receiverRef, value);
|
|
916
|
+
if (mutable.staged)
|
|
917
|
+
this.closeValueAsync(previous, null, (closeFault) => {
|
|
918
|
+
if (closeFault)
|
|
919
|
+
this.closeValueAsync(result, this.faultText(closeFault), (resultFault) => settle(UNIT, combineFaults(closeFault, resultFault)), act.context.actor, act.rcOwner);
|
|
920
|
+
else
|
|
921
|
+
settle(result, null);
|
|
922
|
+
}, act.context.actor, act.rcOwner);
|
|
923
|
+
else
|
|
924
|
+
settle(result, fault);
|
|
925
|
+
}
|
|
807
926
|
};
|
|
808
927
|
const context = {
|
|
809
928
|
id: task.id,
|
|
810
929
|
actor: act.context.actor,
|
|
930
|
+
rcOwner: act.rcOwner,
|
|
811
931
|
requestDeadline: this.requestDeadline(act),
|
|
812
|
-
returned: (value) =>
|
|
813
|
-
|
|
932
|
+
returned: (value) => {
|
|
933
|
+
if (dynamicEntry?.receiver_handback &&
|
|
934
|
+
dynamicReceiver?.kind === "dyn") {
|
|
935
|
+
if (value.kind !== "record" || value.fields.length !== 2)
|
|
936
|
+
throw new Error("prepared dynamic method omitted its dual result");
|
|
937
|
+
dynamicReceiver.value = value.fields[1];
|
|
938
|
+
settle(value.fields[0], null);
|
|
939
|
+
}
|
|
940
|
+
else if (!mutable)
|
|
941
|
+
settle(value, null);
|
|
942
|
+
else if (value.kind === "record" && value.fields.length === 2)
|
|
943
|
+
handback(value.fields[1], null, value.fields[0]);
|
|
944
|
+
else
|
|
945
|
+
throw new Error("prepared mutable method returned no dual result");
|
|
946
|
+
},
|
|
947
|
+
failed: (fault, value) => {
|
|
948
|
+
if (dynamicEntry?.receiver_handback &&
|
|
949
|
+
dynamicReceiver?.kind === "dyn") {
|
|
950
|
+
if (!value)
|
|
951
|
+
throw new Error("prepared dynamic method omitted its fault handback");
|
|
952
|
+
dynamicReceiver.value = value;
|
|
953
|
+
}
|
|
954
|
+
if (mutable)
|
|
955
|
+
handback(value ?? null, fault, UNIT);
|
|
956
|
+
else
|
|
957
|
+
settle(UNIT, fault);
|
|
958
|
+
},
|
|
814
959
|
};
|
|
815
|
-
const frame = this.activate(this.functionAt(calleeFunction(callable)), callable.kind === "closure" ? [callable.environment] :
|
|
816
|
-
let started = false;
|
|
960
|
+
const frame = this.activate(this.functionAt(calleeFunction(callable, specialization)), callable.kind === "closure" ? [callable.environment, ...args] : args, null, null, undefined, null, context);
|
|
817
961
|
task.cancel = () => {
|
|
818
962
|
if (task.done || task.finishing)
|
|
819
963
|
return;
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
cancelled: true,
|
|
827
|
-
});
|
|
964
|
+
context.pendingCancellation ??= {
|
|
965
|
+
kind: "panic",
|
|
966
|
+
message: "task cancelled",
|
|
967
|
+
cancelled: true,
|
|
968
|
+
};
|
|
969
|
+
context.cancel?.(context.pendingCancellation);
|
|
828
970
|
};
|
|
829
971
|
this.scheduler.enqueue(task.id, () => {
|
|
830
972
|
if (task.done || task.finishing)
|
|
831
973
|
return;
|
|
832
|
-
started = true;
|
|
833
974
|
this.runFrame(frame);
|
|
834
975
|
});
|
|
835
976
|
this.trace.allocateId("task", task.id, act.context.id);
|
|
@@ -906,14 +1047,7 @@ class ExecutorV1 {
|
|
|
906
1047
|
if (receiver.kind !== "dyn") {
|
|
907
1048
|
throw new TypeError(`dyn.call expected a trait object, got ${receiver.kind}`);
|
|
908
1049
|
}
|
|
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
|
-
}
|
|
1050
|
+
const entry = this.dynamicEntry(receiver, term.slot);
|
|
917
1051
|
// The wrapped value is the method's receiver, ahead of the call's own
|
|
918
1052
|
// arguments, passed under the decision the slot records: the erasure
|
|
919
1053
|
// the concrete type was made under, not something read off the method.
|
|
@@ -993,6 +1127,9 @@ class ExecutorV1 {
|
|
|
993
1127
|
[
|
|
994
1128
|
"RcNew",
|
|
995
1129
|
"RcClone",
|
|
1130
|
+
"RcDowngrade",
|
|
1131
|
+
"WeakCloneRc",
|
|
1132
|
+
"WeakUpgradeRc",
|
|
996
1133
|
"RcGet",
|
|
997
1134
|
"RcIsUnique",
|
|
998
1135
|
"RcSet",
|
|
@@ -1000,7 +1137,7 @@ class ExecutorV1 {
|
|
|
1000
1137
|
"RcWeakCount",
|
|
1001
1138
|
].includes(entry.family)) {
|
|
1002
1139
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1003
|
-
this.runValueProgram(act, term, args, sharedOperation(entry.family, args));
|
|
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] : [])));
|
|
1004
1141
|
return;
|
|
1005
1142
|
}
|
|
1006
1143
|
if (entry?.family === "SupervisorPool") {
|
|
@@ -1016,13 +1153,40 @@ class ExecutorV1 {
|
|
|
1016
1153
|
else
|
|
1017
1154
|
this.completeShim(act, term, UNIT);
|
|
1018
1155
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1019
|
-
});
|
|
1156
|
+
}, act.context.actor, act.rcOwner);
|
|
1020
1157
|
return;
|
|
1021
1158
|
}
|
|
1022
1159
|
if (entry?.family === "StructuralFormat") {
|
|
1023
1160
|
this.structuralFormat(act, term);
|
|
1024
1161
|
return;
|
|
1025
1162
|
}
|
|
1163
|
+
if (entry?.family === "BytesDecodeUtf8") {
|
|
1164
|
+
const value = decodeUtf8Checked(this.boundary(act, term.args[0]));
|
|
1165
|
+
let result;
|
|
1166
|
+
if ("value" in value)
|
|
1167
|
+
result = this.variant(term.result_shape, "ResultOk", [
|
|
1168
|
+
stringValue(value.value),
|
|
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
|
+
{
|
|
1175
|
+
kind: "record",
|
|
1176
|
+
typeId: error.name,
|
|
1177
|
+
shape: error.id,
|
|
1178
|
+
fields: [
|
|
1179
|
+
{ kind: "i64", value: BigInt(value.validUpTo) },
|
|
1180
|
+
this.variant(shapes.error_len, value.errorLen === null ? "OptionNone" : "OptionSome", value.errorLen === null
|
|
1181
|
+
? []
|
|
1182
|
+
: [{ kind: "i64", value: BigInt(value.errorLen) }]),
|
|
1183
|
+
],
|
|
1184
|
+
},
|
|
1185
|
+
]);
|
|
1186
|
+
}
|
|
1187
|
+
this.completeShim(act, term, result);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1026
1190
|
const shim = entry ? resolveRuntimeShim(entry) : undefined;
|
|
1027
1191
|
if (!entry || !shim) {
|
|
1028
1192
|
throw new Error(`runtime family ${term.family} resolved no shim after admission`);
|
|
@@ -1081,6 +1245,7 @@ class ExecutorV1 {
|
|
|
1081
1245
|
this.takeEdge(act, act.fault ? term.fault : term.normal);
|
|
1082
1246
|
return;
|
|
1083
1247
|
case "enter_defer":
|
|
1248
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) + 1;
|
|
1084
1249
|
// The deferred body runs on its own fault state: a body reached while
|
|
1085
1250
|
// unwinding must not read the fault it is cleaning up after.
|
|
1086
1251
|
act.parked.push(act.fault);
|
|
@@ -1088,6 +1253,7 @@ class ExecutorV1 {
|
|
|
1088
1253
|
this.takeEdge(act, term.body);
|
|
1089
1254
|
return;
|
|
1090
1255
|
case "finish_defer": {
|
|
1256
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) - 1;
|
|
1091
1257
|
// Combine the parked fault with whatever the body left, keeping the
|
|
1092
1258
|
// first: a join must never forget the fault the boundary carried in.
|
|
1093
1259
|
const parked = act.parked.pop();
|
|
@@ -1117,12 +1283,7 @@ class ExecutorV1 {
|
|
|
1117
1283
|
typeId: shape.name,
|
|
1118
1284
|
shape: shape.id,
|
|
1119
1285
|
tag: deadline ? term.deadline_variant : term.fault_variant,
|
|
1120
|
-
payload: [
|
|
1121
|
-
{
|
|
1122
|
-
kind: "string",
|
|
1123
|
-
value: faultReport(fault),
|
|
1124
|
-
},
|
|
1125
|
-
],
|
|
1286
|
+
payload: [stringValue(faultReport(fault))],
|
|
1126
1287
|
};
|
|
1127
1288
|
act.fault = null;
|
|
1128
1289
|
if (act.context.actor)
|
|
@@ -1217,7 +1378,7 @@ class ExecutorV1 {
|
|
|
1217
1378
|
this.keepInputs(act, term, args);
|
|
1218
1379
|
this.raiseFault(act, fault, term.unwind);
|
|
1219
1380
|
resume();
|
|
1220
|
-
});
|
|
1381
|
+
}, act.context.actor, act.rcOwner);
|
|
1221
1382
|
};
|
|
1222
1383
|
const next = (value = UNIT) => {
|
|
1223
1384
|
let step;
|
|
@@ -1235,6 +1396,9 @@ class ExecutorV1 {
|
|
|
1235
1396
|
this.completeShim(act, term, step.value);
|
|
1236
1397
|
resume();
|
|
1237
1398
|
}
|
|
1399
|
+
else if (step.value.kind === "fault") {
|
|
1400
|
+
fail({ kind: "panic", message: step.value.message });
|
|
1401
|
+
}
|
|
1238
1402
|
else if (step.value.kind === "release") {
|
|
1239
1403
|
const request = step.value;
|
|
1240
1404
|
this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
|
|
@@ -1248,11 +1412,11 @@ class ExecutorV1 {
|
|
|
1248
1412
|
act.fault ??= fault;
|
|
1249
1413
|
}
|
|
1250
1414
|
next();
|
|
1251
|
-
});
|
|
1415
|
+
}, act.context.actor, act.rcOwner);
|
|
1252
1416
|
}
|
|
1253
1417
|
else {
|
|
1254
1418
|
const request = step.value;
|
|
1255
|
-
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail);
|
|
1419
|
+
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner);
|
|
1256
1420
|
act.context.cancel = (fault) => child.cancel?.(fault);
|
|
1257
1421
|
}
|
|
1258
1422
|
};
|
|
@@ -1261,8 +1425,17 @@ class ExecutorV1 {
|
|
|
1261
1425
|
}
|
|
1262
1426
|
supervisorPool(act, term, operation) {
|
|
1263
1427
|
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
|
|
1428
|
+
if (view?.kind !== "vector")
|
|
1429
|
+
throw new Error("pool operation has no view");
|
|
1430
|
+
if (operation === "Len") {
|
|
1431
|
+
this.completeShim(act, term, {
|
|
1432
|
+
kind: "i64",
|
|
1433
|
+
value: BigInt(view.items.length),
|
|
1434
|
+
});
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (index?.kind !== "i64")
|
|
1438
|
+
throw new Error("pool operation has no index");
|
|
1266
1439
|
const value = view.items[Number(index.value)];
|
|
1267
1440
|
if (operation === "Get") {
|
|
1268
1441
|
this.completeShim(act, term, this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : []));
|
|
@@ -1282,7 +1455,7 @@ class ExecutorV1 {
|
|
|
1282
1455
|
const continueRendering = () => {
|
|
1283
1456
|
const next = renderer.next();
|
|
1284
1457
|
if (typeof next === "string") {
|
|
1285
|
-
this.completeShim(act, term,
|
|
1458
|
+
this.completeShim(act, term, stringValue(next));
|
|
1286
1459
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1287
1460
|
return;
|
|
1288
1461
|
}
|
|
@@ -1307,7 +1480,92 @@ class ExecutorV1 {
|
|
|
1307
1480
|
}
|
|
1308
1481
|
this.takeEdge(act, term.normal);
|
|
1309
1482
|
}
|
|
1483
|
+
sweepRcOwner(owner, actor, done) {
|
|
1484
|
+
let firstFault = null;
|
|
1485
|
+
let count = 0;
|
|
1486
|
+
const byType = new Map();
|
|
1487
|
+
const schedule = (work) => this.scheduler.enqueue(actor?.id ?? "actor:root", work);
|
|
1488
|
+
const next = () => {
|
|
1489
|
+
const blocks = owner.blocks;
|
|
1490
|
+
owner.blocks = new Set();
|
|
1491
|
+
if (!blocks.size) {
|
|
1492
|
+
owner.closed = true;
|
|
1493
|
+
if (actor && count)
|
|
1494
|
+
this.trace.snapshot("actor.rc_sweep", {
|
|
1495
|
+
actor_id: actor.id,
|
|
1496
|
+
rc_sweep: {
|
|
1497
|
+
count: String(count),
|
|
1498
|
+
by_type: [...byType]
|
|
1499
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1500
|
+
.map(([type_name, amount]) => ({
|
|
1501
|
+
type_name,
|
|
1502
|
+
count: String(amount),
|
|
1503
|
+
})),
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
done(firstFault);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const marking = blocks.values();
|
|
1510
|
+
const payloads = blocks.values();
|
|
1511
|
+
let remaining = 64;
|
|
1512
|
+
const release = () => {
|
|
1513
|
+
if (!remaining--) {
|
|
1514
|
+
remaining = 64;
|
|
1515
|
+
schedule(release);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const item = payloads.next();
|
|
1519
|
+
if (item.done) {
|
|
1520
|
+
schedule(next);
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
const cell = item.value;
|
|
1524
|
+
const payload = cell.value;
|
|
1525
|
+
cell.value = UNIT;
|
|
1526
|
+
blocks.delete(cell);
|
|
1527
|
+
this.closeValueAsync(payload, this.faultText(firstFault), (fault) => {
|
|
1528
|
+
firstFault = combineFaults(firstFault, fault);
|
|
1529
|
+
release();
|
|
1530
|
+
}, actor, owner);
|
|
1531
|
+
};
|
|
1532
|
+
const mark = () => {
|
|
1533
|
+
for (let i = 0; i < 64; i++) {
|
|
1534
|
+
const item = marking.next();
|
|
1535
|
+
if (item.done) {
|
|
1536
|
+
release();
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const cell = item.value;
|
|
1540
|
+
cell.dying = true;
|
|
1541
|
+
cell.refs = 0;
|
|
1542
|
+
cell.owner = null;
|
|
1543
|
+
count += 1;
|
|
1544
|
+
byType.set(cell.typeName, (byType.get(cell.typeName) ?? 0) + 1);
|
|
1545
|
+
}
|
|
1546
|
+
schedule(mark);
|
|
1547
|
+
};
|
|
1548
|
+
mark();
|
|
1549
|
+
};
|
|
1550
|
+
next();
|
|
1551
|
+
}
|
|
1552
|
+
finishConstruction(act, done) {
|
|
1553
|
+
if (!act.fn.actor_construction || !act.rcOwner)
|
|
1554
|
+
return false;
|
|
1555
|
+
const owner = act.rcOwner;
|
|
1556
|
+
act.rcOwner = null;
|
|
1557
|
+
this.running = false;
|
|
1558
|
+
this.sweepRcOwner(owner, act.context.actor, (fault) => {
|
|
1559
|
+
act.fault = combineFaults(act.fault, fault);
|
|
1560
|
+
done();
|
|
1561
|
+
const current = this.current;
|
|
1562
|
+
this.scheduler.enqueue(current.context.id, () => this.runFrame(current));
|
|
1563
|
+
});
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1310
1566
|
returnFrom(act, value) {
|
|
1567
|
+
if (this.finishConstruction(act, () => this.returnFrom(act, value)))
|
|
1568
|
+
return;
|
|
1311
1569
|
const caller = act.caller;
|
|
1312
1570
|
if (!caller) {
|
|
1313
1571
|
this.running = false;
|
|
@@ -1332,6 +1590,14 @@ class ExecutorV1 {
|
|
|
1332
1590
|
this.current = caller;
|
|
1333
1591
|
this.takeEdge(caller, act.normal);
|
|
1334
1592
|
}
|
|
1593
|
+
dynamicEntry(receiver, slot) {
|
|
1594
|
+
const table = this.pkg.vtables[receiver.vtable] ??
|
|
1595
|
+
this.pkg.vtables.find((table) => table.id === receiver.vtable);
|
|
1596
|
+
const entry = table?.slots.find((entry) => entry.slot === slot);
|
|
1597
|
+
if (!entry)
|
|
1598
|
+
throw new Error(`vtable ${receiver.vtable} has no slot ${slot}`);
|
|
1599
|
+
return entry;
|
|
1600
|
+
}
|
|
1335
1601
|
/// The wrapped value on its way to a trait method's `self`. A `copy` receiver
|
|
1336
1602
|
/// deep-clones, a `move` invalidates the trait object it came out of, and a
|
|
1337
1603
|
/// borrow passes the live value.
|
|
@@ -1359,6 +1625,8 @@ class ExecutorV1 {
|
|
|
1359
1625
|
this.takeEdge(act, unwind);
|
|
1360
1626
|
}
|
|
1361
1627
|
resumeUnwind(act, handback) {
|
|
1628
|
+
if (this.finishConstruction(act, () => this.resumeUnwind(act, handback)))
|
|
1629
|
+
return;
|
|
1362
1630
|
const fault = act.fault;
|
|
1363
1631
|
if (!fault) {
|
|
1364
1632
|
throw new Error(`${act.fn.name}: resume_unwind with no pending fault`);
|
|
@@ -1366,7 +1634,7 @@ class ExecutorV1 {
|
|
|
1366
1634
|
const caller = act.caller;
|
|
1367
1635
|
if (!caller) {
|
|
1368
1636
|
this.running = false;
|
|
1369
|
-
act.context.failed(fault);
|
|
1637
|
+
act.context.failed(fault, handback);
|
|
1370
1638
|
return;
|
|
1371
1639
|
}
|
|
1372
1640
|
if (!act.unwind) {
|
|
@@ -1436,37 +1704,27 @@ class ExecutorV1 {
|
|
|
1436
1704
|
return;
|
|
1437
1705
|
}
|
|
1438
1706
|
case "StreamSend": {
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
let closing = false;
|
|
1443
|
-
let cancelled = null;
|
|
1444
|
-
const wake = this.park(act, term, () => cancelWait());
|
|
1707
|
+
const sink = this.boundary(act, term.inputs[0]);
|
|
1708
|
+
const value = this.boundary(act, term.inputs[1]);
|
|
1709
|
+
const wake = this.park(act, term);
|
|
1445
1710
|
const cancel = act.context.cancel;
|
|
1711
|
+
const stop = this.sendStream(sink, value, term.detail.park, act.context.actor, (status, fault) => {
|
|
1712
|
+
wake(UNIT, fault, status);
|
|
1713
|
+
}, act.rcOwner);
|
|
1446
1714
|
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);
|
|
1715
|
+
stop(fault, (closeFault) => cancel?.(closeFault ?? fault));
|
|
1456
1716
|
};
|
|
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
1717
|
return;
|
|
1467
1718
|
}
|
|
1468
1719
|
case "StreamNext": {
|
|
1469
1720
|
const stream = this.boundary(act, term.inputs[0]);
|
|
1721
|
+
const adapter = stream.kind === "stream"
|
|
1722
|
+
? this.generatorStreams.get(stream.channelId)
|
|
1723
|
+
: undefined;
|
|
1724
|
+
if (adapter) {
|
|
1725
|
+
this.receiveGeneratorStream(act, term, adapter);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1470
1728
|
let cancelWait = () => { };
|
|
1471
1729
|
const wake = this.park(act, term, () => cancelWait());
|
|
1472
1730
|
cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault) => {
|
|
@@ -1479,10 +1737,13 @@ class ExecutorV1 {
|
|
|
1479
1737
|
}
|
|
1480
1738
|
case "Await": {
|
|
1481
1739
|
const task = this.taskFor(this.boundary(act, term.inputs[0]));
|
|
1482
|
-
|
|
1740
|
+
let unwatch = () => { };
|
|
1741
|
+
const wake = this.park(act, term, () => unwatch());
|
|
1483
1742
|
const ready = () => {
|
|
1484
|
-
if (!task.done)
|
|
1743
|
+
if (!task.done) {
|
|
1744
|
+
this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
|
|
1485
1745
|
return;
|
|
1746
|
+
}
|
|
1486
1747
|
if (task.fault)
|
|
1487
1748
|
wake(UNIT, task.fault);
|
|
1488
1749
|
else {
|
|
@@ -1492,6 +1753,11 @@ class ExecutorV1 {
|
|
|
1492
1753
|
}
|
|
1493
1754
|
};
|
|
1494
1755
|
task.changed.push(ready);
|
|
1756
|
+
unwatch = () => {
|
|
1757
|
+
const index = task.changed.indexOf(ready);
|
|
1758
|
+
if (index >= 0)
|
|
1759
|
+
task.changed.splice(index, 1);
|
|
1760
|
+
};
|
|
1495
1761
|
ready();
|
|
1496
1762
|
return;
|
|
1497
1763
|
}
|
|
@@ -1554,6 +1820,11 @@ class ExecutorV1 {
|
|
|
1554
1820
|
cancelTimer?.();
|
|
1555
1821
|
wake({ kind: "i64", value: BigInt(ready[0].index) });
|
|
1556
1822
|
}
|
|
1823
|
+
else {
|
|
1824
|
+
this.pendingActorWait(act, sources.map((value) => value.kind === "stream"
|
|
1825
|
+
? undefined
|
|
1826
|
+
: this.taskFor(value).actorTarget), hasTimeout, "select", (fault) => wake(UNIT, fault));
|
|
1827
|
+
}
|
|
1557
1828
|
};
|
|
1558
1829
|
if (hasTimeout) {
|
|
1559
1830
|
const duration = values[values.length - 1];
|
|
@@ -1581,18 +1852,39 @@ class ExecutorV1 {
|
|
|
1581
1852
|
const protocol = term.detail;
|
|
1582
1853
|
const inputs = term.inputs.map((input) => this.boundary(act, input));
|
|
1583
1854
|
const wake = this.park(act, term);
|
|
1855
|
+
let target;
|
|
1856
|
+
let checkWait;
|
|
1584
1857
|
const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault) => {
|
|
1585
1858
|
act.fault ??= closeFault ?? null;
|
|
1586
1859
|
return wake(this.completion(term.result_shape, term.error_shape, value, error));
|
|
1587
|
-
}, term.request_shapes)
|
|
1860
|
+
}, term.request_shapes, (actor) => {
|
|
1861
|
+
target = actor;
|
|
1862
|
+
checkWait?.();
|
|
1863
|
+
});
|
|
1588
1864
|
const cancel = act.context.cancel;
|
|
1589
1865
|
act.context.cancel = (fault) => cancelRequest((closeFault) => cancel(closeFault ?? fault));
|
|
1866
|
+
checkWait = () => this.pendingActorWait(act, [target], false, "ask", (fault) => cancelRequest((closeFault) => wake(UNIT, closeFault ?? fault)));
|
|
1867
|
+
checkWait();
|
|
1590
1868
|
return;
|
|
1591
1869
|
}
|
|
1592
1870
|
case "NativeIo": {
|
|
1593
1871
|
if (term.detail?.operation === "SinkFinish") {
|
|
1594
|
-
this.
|
|
1595
|
-
|
|
1872
|
+
const wake = this.park(act, term);
|
|
1873
|
+
const cancel = act.context.cancel;
|
|
1874
|
+
let stopped = null;
|
|
1875
|
+
act.context.cancel = (fault = {
|
|
1876
|
+
kind: "panic",
|
|
1877
|
+
message: "task cancelled",
|
|
1878
|
+
cancelled: true,
|
|
1879
|
+
}) => {
|
|
1880
|
+
stopped ??= fault;
|
|
1881
|
+
};
|
|
1882
|
+
this.finishSink(this.boundary(act, term.inputs[0]), null, () => {
|
|
1883
|
+
if (stopped)
|
|
1884
|
+
cancel(stopped);
|
|
1885
|
+
else
|
|
1886
|
+
wake(UNIT);
|
|
1887
|
+
});
|
|
1596
1888
|
return;
|
|
1597
1889
|
}
|
|
1598
1890
|
// Admission passed `StdinReadLine`. The record is the raw line,
|
|
@@ -1602,6 +1894,40 @@ class ExecutorV1 {
|
|
|
1602
1894
|
this.park(act, term)(bytesValue([...line]));
|
|
1603
1895
|
return;
|
|
1604
1896
|
}
|
|
1897
|
+
case "Safepoint": {
|
|
1898
|
+
if (act.context.pollSuppression) {
|
|
1899
|
+
this.takeEdge(act, term.resumes[0]);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
if (act.context.pendingCancellation && term.cancel) {
|
|
1903
|
+
act.fault = act.context.pendingCancellation;
|
|
1904
|
+
this.takeEdge(act, term.cancel);
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
const budget = term.detail?.budget;
|
|
1908
|
+
if (!Number.isInteger(budget) || budget <= 0)
|
|
1909
|
+
throw new Error("safepoint requires a positive compiler-authored budget");
|
|
1910
|
+
const countdown = act.context.pollBudget;
|
|
1911
|
+
countdown.remaining = (countdown.remaining || budget) - 1;
|
|
1912
|
+
if (countdown.remaining > 0) {
|
|
1913
|
+
this.takeEdge(act, term.resumes[0]);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
countdown.remaining = budget;
|
|
1917
|
+
this.scheduler.advanceSlice();
|
|
1918
|
+
const fault = act.context.pendingCancellation ?? this.scopeDeadline(act);
|
|
1919
|
+
if (fault && term.cancel) {
|
|
1920
|
+
act.fault = fault;
|
|
1921
|
+
this.takeEdge(act, term.cancel);
|
|
1922
|
+
}
|
|
1923
|
+
else if (this.scheduler.hasRunnableWork()) {
|
|
1924
|
+
this.park(act, term)(UNIT);
|
|
1925
|
+
}
|
|
1926
|
+
else {
|
|
1927
|
+
this.takeEdge(act, term.resumes[0]);
|
|
1928
|
+
}
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1605
1931
|
case "Sleep": {
|
|
1606
1932
|
const duration = term.inputs[0]
|
|
1607
1933
|
? this.boundary(act, term.inputs[0])
|
|
@@ -1616,13 +1942,34 @@ class ExecutorV1 {
|
|
|
1616
1942
|
throw new Error(`suspension kind ${term.kind} resolved no handler after admission`);
|
|
1617
1943
|
}
|
|
1618
1944
|
}
|
|
1945
|
+
pendingActorWait(act, targets, externalProgress, operation, fail) {
|
|
1946
|
+
const owner = act.context.actor;
|
|
1947
|
+
// Independent tasks carry actor ownership for cleanup, but do not hold its turn.
|
|
1948
|
+
if (!owner || owner.active !== act.context || !act.context.parked)
|
|
1949
|
+
return;
|
|
1950
|
+
this.actorWaits.ready(owner);
|
|
1951
|
+
if (externalProgress ||
|
|
1952
|
+
this.requestDeadline(act) !== undefined ||
|
|
1953
|
+
targets.some((target) => !target))
|
|
1954
|
+
return;
|
|
1955
|
+
const path = this.actorWaits.pending(owner, targets);
|
|
1956
|
+
if (path)
|
|
1957
|
+
fail({
|
|
1958
|
+
kind: "panic",
|
|
1959
|
+
message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1619
1962
|
park(act, term, cleanup = () => { }) {
|
|
1620
1963
|
this.running = false;
|
|
1964
|
+
act.context.parked = true;
|
|
1621
1965
|
let pending = null;
|
|
1622
1966
|
const resume = (value, fault = null, edge = 0) => {
|
|
1623
1967
|
if (pending)
|
|
1624
1968
|
return false;
|
|
1625
1969
|
pending = { value, fault, edge };
|
|
1970
|
+
act.context.parked = false;
|
|
1971
|
+
if (act.context.actor?.active === act.context)
|
|
1972
|
+
this.actorWaits.ready(act.context.actor);
|
|
1626
1973
|
cleanup();
|
|
1627
1974
|
this.scheduler.enqueue(act.context.id, () => {
|
|
1628
1975
|
const outcome = pending;
|
|
@@ -1631,7 +1978,8 @@ class ExecutorV1 {
|
|
|
1631
1978
|
const fault = closeFault ?? (outcome.fault ? (act.fault ?? outcome.fault) : null);
|
|
1632
1979
|
if (fault) {
|
|
1633
1980
|
act.fault = fault;
|
|
1634
|
-
this.takeEdge(act,
|
|
1981
|
+
this.takeEdge(act, term.kind === "Safepoint" ||
|
|
1982
|
+
(fault.kind === "panic" && fault.cancelled)
|
|
1635
1983
|
? term.cancel
|
|
1636
1984
|
: term.unwind);
|
|
1637
1985
|
}
|
|
@@ -1649,13 +1997,15 @@ class ExecutorV1 {
|
|
|
1649
1997
|
// the cancellation latch live through that window and retire a result
|
|
1650
1998
|
// whose normal edge the consumer never entered.
|
|
1651
1999
|
if (outcome.fault)
|
|
1652
|
-
this.closeValueAsync(outcome.value, this.faultText(act.fault ?? outcome.fault), finish, act.context.actor);
|
|
2000
|
+
this.closeValueAsync(outcome.value, this.faultText(act.fault ?? outcome.fault), finish, act.context.actor, act.rcOwner);
|
|
1653
2001
|
else
|
|
1654
2002
|
finish();
|
|
1655
2003
|
});
|
|
1656
2004
|
return true;
|
|
1657
2005
|
};
|
|
1658
2006
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
2007
|
+
if (term.cancel == null)
|
|
2008
|
+
return;
|
|
1659
2009
|
if (pending)
|
|
1660
2010
|
pending.fault ??= fault;
|
|
1661
2011
|
else
|
|
@@ -1663,6 +2013,222 @@ class ExecutorV1 {
|
|
|
1663
2013
|
};
|
|
1664
2014
|
return resume;
|
|
1665
2015
|
}
|
|
2016
|
+
sinkClosed(value) {
|
|
2017
|
+
const endpoint = value.kind === "sink"
|
|
2018
|
+
? this.senderEndpoints.get(value.channelId)
|
|
2019
|
+
: undefined;
|
|
2020
|
+
return endpoint ? !endpoint.open : this.pipes.isClosed(value);
|
|
2021
|
+
}
|
|
2022
|
+
senderEndpoint(owner, open = true) {
|
|
2023
|
+
const id = this.ids.channel();
|
|
2024
|
+
owner.references++;
|
|
2025
|
+
if (open)
|
|
2026
|
+
owner.endpoints++;
|
|
2027
|
+
this.senderEndpoints.set(id, { owner, open });
|
|
2028
|
+
return { kind: "sink", channelId: id };
|
|
2029
|
+
}
|
|
2030
|
+
finishSink(value, fault, done) {
|
|
2031
|
+
const endpoint = value.kind === "sink"
|
|
2032
|
+
? this.senderEndpoints.get(value.channelId)
|
|
2033
|
+
: undefined;
|
|
2034
|
+
if (!endpoint) {
|
|
2035
|
+
this.pipes.close(value, fault);
|
|
2036
|
+
done();
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
if (!endpoint.open) {
|
|
2040
|
+
done();
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
endpoint.open = false;
|
|
2044
|
+
endpoint.owner.endpoints--;
|
|
2045
|
+
if (fault || endpoint.owner.endpoints === 0) {
|
|
2046
|
+
const finish = () => {
|
|
2047
|
+
if (endpoint.owner.busy) {
|
|
2048
|
+
endpoint.owner.waiting.add(finish);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
this.finishSink(endpoint.owner.sink, fault, done);
|
|
2052
|
+
};
|
|
2053
|
+
finish();
|
|
2054
|
+
}
|
|
2055
|
+
else
|
|
2056
|
+
done();
|
|
2057
|
+
}
|
|
2058
|
+
sendStream(sink, value, park, actor, complete, rcOwner) {
|
|
2059
|
+
const endpoint = sink.kind === "sink"
|
|
2060
|
+
? this.senderEndpoints.get(sink.channelId)
|
|
2061
|
+
: undefined;
|
|
2062
|
+
let owned = true;
|
|
2063
|
+
let finished = false;
|
|
2064
|
+
let closing = false;
|
|
2065
|
+
let claimed = false;
|
|
2066
|
+
let stopped = null;
|
|
2067
|
+
let encoder = null;
|
|
2068
|
+
let child = null;
|
|
2069
|
+
let withdraw = () => { };
|
|
2070
|
+
const drained = [];
|
|
2071
|
+
const release = () => {
|
|
2072
|
+
if (!claimed || !endpoint)
|
|
2073
|
+
return;
|
|
2074
|
+
claimed = false;
|
|
2075
|
+
endpoint.owner.busy = false;
|
|
2076
|
+
for (const wake of endpoint.owner.waiting)
|
|
2077
|
+
this.scheduler.enqueue(actor?.id ?? "actor:root", wake);
|
|
2078
|
+
endpoint.owner.waiting.clear();
|
|
2079
|
+
};
|
|
2080
|
+
const finish = (status, fault = null) => {
|
|
2081
|
+
if (finished || closing)
|
|
2082
|
+
return;
|
|
2083
|
+
closing = true;
|
|
2084
|
+
release();
|
|
2085
|
+
const done = (closeFault = null) => {
|
|
2086
|
+
finished = true;
|
|
2087
|
+
const failure = combineFaults(fault, closeFault);
|
|
2088
|
+
if (stopped)
|
|
2089
|
+
for (const wake of drained.splice(0))
|
|
2090
|
+
wake(failure);
|
|
2091
|
+
else
|
|
2092
|
+
complete(status, failure);
|
|
2093
|
+
};
|
|
2094
|
+
if (owned) {
|
|
2095
|
+
owned = false;
|
|
2096
|
+
this.closeValueAsync(value, this.faultText(fault ?? stopped), done, actor, rcOwner);
|
|
2097
|
+
}
|
|
2098
|
+
else
|
|
2099
|
+
done();
|
|
2100
|
+
};
|
|
2101
|
+
const start = () => {
|
|
2102
|
+
if (stopped || finished || closing)
|
|
2103
|
+
return;
|
|
2104
|
+
if (!endpoint) {
|
|
2105
|
+
withdraw = this.pipes.send(sink, value, park, (status) => {
|
|
2106
|
+
if (status === 0)
|
|
2107
|
+
owned = false;
|
|
2108
|
+
finish(status);
|
|
2109
|
+
});
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (!endpoint.open) {
|
|
2113
|
+
finish(1);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const owner = endpoint.owner;
|
|
2117
|
+
if (owner.busy) {
|
|
2118
|
+
if (!park) {
|
|
2119
|
+
finish(2);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
owner.waiting.add(start);
|
|
2123
|
+
withdraw = () => {
|
|
2124
|
+
owner.waiting.delete(start);
|
|
2125
|
+
};
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
owner.busy = true;
|
|
2129
|
+
claimed = true;
|
|
2130
|
+
const callable = owner.encoder;
|
|
2131
|
+
encoder = this.invokeFrame(actor, calleeFunction(callable, null), callable.kind === "closure" ? [callable.environment, value] : [value], (encoded) => {
|
|
2132
|
+
encoder = null;
|
|
2133
|
+
if (stopped) {
|
|
2134
|
+
this.closeValueAsync(encoded, this.faultText(stopped), (fault) => finish(0, fault), actor, rcOwner);
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
child = this.sendStream(owner.sink, encoded, park, actor, (status, fault) => {
|
|
2138
|
+
child = null;
|
|
2139
|
+
finish(status, fault);
|
|
2140
|
+
}, rcOwner);
|
|
2141
|
+
}
|
|
2142
|
+
}, (fault) => {
|
|
2143
|
+
encoder = null;
|
|
2144
|
+
finish(3, fault);
|
|
2145
|
+
}, rcOwner);
|
|
2146
|
+
};
|
|
2147
|
+
start();
|
|
2148
|
+
return (fault, done) => {
|
|
2149
|
+
if (finished) {
|
|
2150
|
+
done(null);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
stopped ??= fault;
|
|
2154
|
+
drained.push(done);
|
|
2155
|
+
withdraw();
|
|
2156
|
+
if (closing)
|
|
2157
|
+
return;
|
|
2158
|
+
if (encoder)
|
|
2159
|
+
encoder.cancel?.(fault);
|
|
2160
|
+
else if (child)
|
|
2161
|
+
child(fault, (failure) => {
|
|
2162
|
+
child = null;
|
|
2163
|
+
finish(0, failure);
|
|
2164
|
+
});
|
|
2165
|
+
else
|
|
2166
|
+
finish(0);
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
receiveGeneratorStream(act, term, adapter) {
|
|
2170
|
+
const wake = this.park(act, term);
|
|
2171
|
+
const receive = (value, fault) => {
|
|
2172
|
+
if (fault)
|
|
2173
|
+
wake(UNIT, fault);
|
|
2174
|
+
else
|
|
2175
|
+
wake(this.variant(term.result_shape, value === null ? "OptionNone" : "OptionSome", value === null ? [] : [value]));
|
|
2176
|
+
};
|
|
2177
|
+
if (adapter.pending !== null) {
|
|
2178
|
+
const value = adapter.pending;
|
|
2179
|
+
adapter.pending = null;
|
|
2180
|
+
receive(value, null);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const generator = adapter.generator;
|
|
2184
|
+
if (generator.done) {
|
|
2185
|
+
receive(null, adapter.fault ?? generator.fault);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (term.detail.park)
|
|
2189
|
+
adapter.waiting = receive;
|
|
2190
|
+
else
|
|
2191
|
+
receive(null, null);
|
|
2192
|
+
const cancel = act.context.cancel;
|
|
2193
|
+
if (term.detail.park)
|
|
2194
|
+
act.context.cancel = (fault) => {
|
|
2195
|
+
adapter.waiting = undefined;
|
|
2196
|
+
this.closeGenerator(generator, (closeFault) => cancel?.(closeFault ?? fault));
|
|
2197
|
+
};
|
|
2198
|
+
if (adapter.receiving)
|
|
2199
|
+
return;
|
|
2200
|
+
adapter.receiving = true;
|
|
2201
|
+
generator.receive = (value, fault) => {
|
|
2202
|
+
adapter.receiving = false;
|
|
2203
|
+
if (adapter.closed) {
|
|
2204
|
+
if (value !== null)
|
|
2205
|
+
this.closeValueAsync(value, null, () => { });
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
adapter.fault = fault;
|
|
2209
|
+
const waiting = adapter.waiting;
|
|
2210
|
+
adapter.waiting = undefined;
|
|
2211
|
+
if (waiting)
|
|
2212
|
+
waiting(value, fault);
|
|
2213
|
+
else
|
|
2214
|
+
adapter.pending = value;
|
|
2215
|
+
};
|
|
2216
|
+
if (generator.started) {
|
|
2217
|
+
const resume = generator.resume;
|
|
2218
|
+
generator.resume = undefined;
|
|
2219
|
+
if (!resume)
|
|
2220
|
+
throw new Error("generator stream lost its suspended yield");
|
|
2221
|
+
resume();
|
|
2222
|
+
}
|
|
2223
|
+
else {
|
|
2224
|
+
this.scheduler.enqueue(generator.id, () => {
|
|
2225
|
+
if (generator.done)
|
|
2226
|
+
return;
|
|
2227
|
+
generator.started = true;
|
|
2228
|
+
this.runFrame(generator.frame);
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
1666
2232
|
newPipe(capacity) {
|
|
1667
2233
|
const id = this.ids.channel();
|
|
1668
2234
|
this.trace.allocateId("channel", id, this.current?.context.id ?? "actor:root");
|
|
@@ -1688,7 +2254,7 @@ class ExecutorV1 {
|
|
|
1688
2254
|
act.fault = combineFaults(act.fault, fault);
|
|
1689
2255
|
if (!synchronous)
|
|
1690
2256
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1691
|
-
});
|
|
2257
|
+
}, act.context.actor, act.rcOwner);
|
|
1692
2258
|
synchronous = false;
|
|
1693
2259
|
if (!completed)
|
|
1694
2260
|
this.running = false;
|
|
@@ -1713,7 +2279,7 @@ class ExecutorV1 {
|
|
|
1713
2279
|
receive?.(null, generator.fault);
|
|
1714
2280
|
for (const closed of generator.closed.splice(0))
|
|
1715
2281
|
closed(generator.fault);
|
|
1716
|
-
});
|
|
2282
|
+
}, generator.frame.context.actor, generator.frame.rcOwner);
|
|
1717
2283
|
}
|
|
1718
2284
|
closeGenerator(generator, done) {
|
|
1719
2285
|
if (generator.done) {
|
|
@@ -1726,7 +2292,7 @@ class ExecutorV1 {
|
|
|
1726
2292
|
else
|
|
1727
2293
|
generator.frame.context.cancel?.();
|
|
1728
2294
|
}
|
|
1729
|
-
closeValueAsync(value, fault, done, actor = this.current.context.actor) {
|
|
2295
|
+
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner) {
|
|
1730
2296
|
const pending = [value];
|
|
1731
2297
|
let firstFault = null;
|
|
1732
2298
|
const next = (failure = null) => {
|
|
@@ -1737,15 +2303,35 @@ class ExecutorV1 {
|
|
|
1737
2303
|
while (pending.length) {
|
|
1738
2304
|
const value = pending.pop();
|
|
1739
2305
|
switch (value.kind) {
|
|
2306
|
+
case "string":
|
|
2307
|
+
value.release();
|
|
2308
|
+
break;
|
|
1740
2309
|
case "rc":
|
|
1741
2310
|
if (!value.closed) {
|
|
1742
2311
|
value.closed = true;
|
|
1743
|
-
if (--value.cell.refs === 0) {
|
|
2312
|
+
if (value.cell && !value.cell.dying && --value.cell.refs === 0) {
|
|
2313
|
+
value.cell.owner?.blocks.delete(value.cell);
|
|
2314
|
+
value.cell.owner = null;
|
|
1744
2315
|
pending.push(value.cell.value);
|
|
1745
2316
|
value.cell.value = UNIT;
|
|
1746
2317
|
}
|
|
1747
2318
|
}
|
|
1748
2319
|
break;
|
|
2320
|
+
case "weak":
|
|
2321
|
+
if (!value.closed) {
|
|
2322
|
+
value.closed = true;
|
|
2323
|
+
value.cell.weak -= 1;
|
|
2324
|
+
}
|
|
2325
|
+
break;
|
|
2326
|
+
case "timer": {
|
|
2327
|
+
if (value.closed)
|
|
2328
|
+
break;
|
|
2329
|
+
const resource = this.pkg.resources?.[value.resource];
|
|
2330
|
+
if (resource?.close === undefined)
|
|
2331
|
+
throw new Error("timer lost its selected close");
|
|
2332
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
1749
2335
|
case "task": {
|
|
1750
2336
|
// Dropping a handle does not cancel scoped work. Join drains the
|
|
1751
2337
|
// task, and scope close releases any result that was never taken.
|
|
@@ -1753,7 +2339,7 @@ class ExecutorV1 {
|
|
|
1753
2339
|
if (task?.done) {
|
|
1754
2340
|
const result = task.value;
|
|
1755
2341
|
task.value = UNIT;
|
|
1756
|
-
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor);
|
|
2342
|
+
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner);
|
|
1757
2343
|
return;
|
|
1758
2344
|
}
|
|
1759
2345
|
if (task?.close) {
|
|
@@ -1781,7 +2367,7 @@ class ExecutorV1 {
|
|
|
1781
2367
|
if (this.consumedResources.has(value))
|
|
1782
2368
|
break;
|
|
1783
2369
|
if (!this.closingResources.has(value)) {
|
|
1784
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next);
|
|
2370
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
1785
2371
|
return;
|
|
1786
2372
|
}
|
|
1787
2373
|
this.consumedResources.add(value);
|
|
@@ -1795,10 +2381,36 @@ class ExecutorV1 {
|
|
|
1795
2381
|
case "vector":
|
|
1796
2382
|
pending.push(...[...value.items].reverse());
|
|
1797
2383
|
break;
|
|
1798
|
-
case "
|
|
1799
|
-
|
|
2384
|
+
case "stream": {
|
|
2385
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2386
|
+
if (adapter) {
|
|
2387
|
+
this.generatorStreams.delete(value.channelId);
|
|
2388
|
+
adapter.closed = true;
|
|
2389
|
+
if (adapter.pending !== null)
|
|
2390
|
+
pending.push(adapter.pending);
|
|
2391
|
+
adapter.pending = null;
|
|
2392
|
+
adapter.waiting = undefined;
|
|
2393
|
+
this.closeGenerator(adapter.generator, next);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
1800
2396
|
pending.push(...this.pipes.close(value, currentFault).reverse());
|
|
1801
2397
|
break;
|
|
2398
|
+
}
|
|
2399
|
+
case "sink": {
|
|
2400
|
+
const endpoint = this.senderEndpoints.get(value.channelId);
|
|
2401
|
+
if (endpoint) {
|
|
2402
|
+
this.finishSink(value, currentFault, () => {
|
|
2403
|
+
this.senderEndpoints.delete(value.channelId);
|
|
2404
|
+
if (--endpoint.owner.references === 0)
|
|
2405
|
+
pending.push(endpoint.owner.sink, endpoint.owner.encoder);
|
|
2406
|
+
next();
|
|
2407
|
+
});
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
else
|
|
2411
|
+
pending.push(...this.pipes.close(value, currentFault).reverse());
|
|
2412
|
+
break;
|
|
2413
|
+
}
|
|
1802
2414
|
}
|
|
1803
2415
|
}
|
|
1804
2416
|
done(firstFault);
|
|
@@ -1806,6 +2418,8 @@ class ExecutorV1 {
|
|
|
1806
2418
|
next();
|
|
1807
2419
|
}
|
|
1808
2420
|
closeValue(value, fault) {
|
|
2421
|
+
if (value.kind === "string")
|
|
2422
|
+
value.release();
|
|
1809
2423
|
if (value.kind === "closure")
|
|
1810
2424
|
this.closeValue(value.environment, fault);
|
|
1811
2425
|
else if (value.kind === "sink" || value.kind === "stream")
|
|
@@ -1854,25 +2468,33 @@ class ExecutorV1 {
|
|
|
1854
2468
|
for (const changed of [...task.changed])
|
|
1855
2469
|
changed();
|
|
1856
2470
|
}
|
|
1857
|
-
invokeFrame(actor, callable, args, returned, failed) {
|
|
1858
|
-
const
|
|
2471
|
+
invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false) {
|
|
2472
|
+
const context = {
|
|
1859
2473
|
id: actor?.id ?? "actor:root",
|
|
1860
2474
|
actor,
|
|
2475
|
+
rcOwner,
|
|
1861
2476
|
returned,
|
|
1862
|
-
failed,
|
|
1863
|
-
}
|
|
2477
|
+
failed: (fault) => failed(fault),
|
|
2478
|
+
};
|
|
2479
|
+
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
1864
2480
|
let cancelled = null;
|
|
1865
|
-
|
|
2481
|
+
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1866
2482
|
cancelled = fault;
|
|
1867
2483
|
};
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2484
|
+
if (inline) {
|
|
2485
|
+
this.current = frame;
|
|
2486
|
+
this.running = true;
|
|
2487
|
+
}
|
|
2488
|
+
else {
|
|
2489
|
+
this.scheduler.enqueue(frame.context.id, () => {
|
|
2490
|
+
context.cancel = undefined;
|
|
2491
|
+
if (cancelled && frame.context === context)
|
|
2492
|
+
context.failed(cancelled);
|
|
2493
|
+
else
|
|
2494
|
+
this.runFrame(frame);
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
return context;
|
|
1876
2498
|
}
|
|
1877
2499
|
variant(shapeId, role, payload = []) {
|
|
1878
2500
|
const shape = shapeId == null ? undefined : this.pkg.variants[shapeId];
|
|
@@ -1920,12 +2542,21 @@ class ExecutorV1 {
|
|
|
1920
2542
|
? { kind: "Actor", identity: act.context.actor.identity }
|
|
1921
2543
|
: { kind: "Task" };
|
|
1922
2544
|
switch (operation.op) {
|
|
2545
|
+
case "identity": {
|
|
2546
|
+
const [left, right] = args;
|
|
2547
|
+
this.completeShim(act, term, {
|
|
2548
|
+
kind: "bool",
|
|
2549
|
+
value: actorIdentical(left, right),
|
|
2550
|
+
});
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
1923
2553
|
case "spawn": {
|
|
1924
2554
|
const layout = this.pkg.actors?.find((actor) => actor.id === operation.actor);
|
|
1925
2555
|
if (!layout)
|
|
1926
2556
|
throw new Error(`actor layout ${operation.actor} is missing`);
|
|
1927
2557
|
let supplied = 0;
|
|
1928
2558
|
const actor = {
|
|
2559
|
+
rcOwner: act.rcOwner ?? { blocks: new Set(), closed: false },
|
|
1929
2560
|
identity: ++this.actorIdentity,
|
|
1930
2561
|
id: this.ids.actor(),
|
|
1931
2562
|
layout,
|
|
@@ -1935,9 +2566,11 @@ class ExecutorV1 {
|
|
|
1935
2566
|
fields: layout.state_fields.map((field) => field.deferred ? UNIT : (args[supplied++] ?? UNIT)),
|
|
1936
2567
|
},
|
|
1937
2568
|
mailbox: [],
|
|
2569
|
+
timers: new Set(),
|
|
1938
2570
|
disposals: [],
|
|
1939
2571
|
admission: [],
|
|
1940
2572
|
busy: true,
|
|
2573
|
+
constructing: true,
|
|
1941
2574
|
alive: true,
|
|
1942
2575
|
completed: false,
|
|
1943
2576
|
closing: false,
|
|
@@ -1947,44 +2580,181 @@ class ExecutorV1 {
|
|
|
1947
2580
|
closingFault: null,
|
|
1948
2581
|
crashing: null,
|
|
1949
2582
|
active: null,
|
|
1950
|
-
|
|
2583
|
+
terminalHook: null,
|
|
1951
2584
|
closed: [],
|
|
1952
2585
|
};
|
|
1953
2586
|
this.actors.set(actor.id, actor);
|
|
1954
2587
|
this.trace.allocateId("actor", actor.id, act.context.id);
|
|
1955
2588
|
this.trace.snapshot("actor.spawn", { actor_id: actor.id });
|
|
1956
2589
|
const handle = { kind: "actor", id: actor.id };
|
|
1957
|
-
this.running = false;
|
|
1958
2590
|
const failed = (fault) => {
|
|
2591
|
+
actor.active = null;
|
|
1959
2592
|
actor.alive = false;
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2593
|
+
actor.constructing = false;
|
|
2594
|
+
act.rcOwner = null;
|
|
2595
|
+
this.cancelActorTimers(actor);
|
|
2596
|
+
this.drainMailbox(actor, this.faultText(fault), (payloadFault) => {
|
|
2597
|
+
const primary = combineFaults(fault, payloadFault);
|
|
2598
|
+
this.closeValueAsync(actor.state, this.faultText(primary), (stateFault) => {
|
|
2599
|
+
const retained = combineFaults(primary, stateFault);
|
|
2600
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
2601
|
+
actor.completed = true;
|
|
2602
|
+
for (const closed of actor.closed.splice(0))
|
|
2603
|
+
closed();
|
|
2604
|
+
this.raiseFault(act, combineFaults(retained, sweepFault), term.unwind);
|
|
2605
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2606
|
+
});
|
|
2607
|
+
}, actor, actor.rcOwner);
|
|
1968
2608
|
});
|
|
1969
2609
|
};
|
|
1970
2610
|
const started = () => {
|
|
2611
|
+
if (actor.closing) {
|
|
2612
|
+
failed(actor.active?.pendingCancellation ?? {
|
|
2613
|
+
kind: "panic",
|
|
2614
|
+
message: "actor construction cancelled",
|
|
2615
|
+
cancelled: true,
|
|
2616
|
+
});
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
actor.constructing = false;
|
|
2620
|
+
actor.active = null;
|
|
2621
|
+
act.rcOwner = null;
|
|
1971
2622
|
actor.busy = false;
|
|
2623
|
+
this.startPeriodicTimers(actor);
|
|
1972
2624
|
this.dispatchActor(actor);
|
|
1973
2625
|
this.completeShim(act, term, handle);
|
|
1974
|
-
this.
|
|
2626
|
+
this.current = act;
|
|
2627
|
+
this.running = true;
|
|
1975
2628
|
};
|
|
2629
|
+
// Construction enters the same interpreter loop as its caller. Only
|
|
2630
|
+
// a real suspension queues work and advances deterministic scheduling.
|
|
1976
2631
|
const start = () => {
|
|
1977
2632
|
if (layout.start !== undefined)
|
|
1978
|
-
this.invokeFrame(actor, layout.start, [actor.state], started, failed);
|
|
2633
|
+
actor.active = this.invokeFrame(actor, layout.start, [actor.state], started, failed, actor.rcOwner, true);
|
|
1979
2634
|
else
|
|
1980
2635
|
started();
|
|
1981
2636
|
};
|
|
1982
2637
|
if (layout.init !== undefined)
|
|
1983
|
-
this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed);
|
|
2638
|
+
actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
|
|
1984
2639
|
else
|
|
1985
2640
|
start();
|
|
1986
2641
|
return;
|
|
1987
2642
|
}
|
|
2643
|
+
case "timer_schedule": {
|
|
2644
|
+
const [target, after, overdue, hasOverdue, payload] = args;
|
|
2645
|
+
const actor = this.actorFor(target);
|
|
2646
|
+
if (actor !== act.context.actor ||
|
|
2647
|
+
after?.kind !== "i64" ||
|
|
2648
|
+
overdue?.kind !== "i64" ||
|
|
2649
|
+
hasOverdue?.kind !== "bool" ||
|
|
2650
|
+
payload?.kind !== "record")
|
|
2651
|
+
throw new Error("timer schedule lost its checked owner or timing");
|
|
2652
|
+
const handler = actor.layout.handlers.find((handler) => handler.message_id === operation.message);
|
|
2653
|
+
if (!handler)
|
|
2654
|
+
throw new Error("timer schedule lost its handler");
|
|
2655
|
+
if (this.rootComplete || actor.closing) {
|
|
2656
|
+
this.running = false;
|
|
2657
|
+
const refused = {
|
|
2658
|
+
kind: "panic",
|
|
2659
|
+
actorSendFailed: true,
|
|
2660
|
+
message: "actor timer admission is closed",
|
|
2661
|
+
};
|
|
2662
|
+
this.closeValueAsync(payload, faultReport(refused), (fault) => {
|
|
2663
|
+
this.raiseFault(act, combineFaults(refused, fault), term.unwind);
|
|
2664
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2665
|
+
}, actor, act.rcOwner);
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
let phase = "scheduled";
|
|
2669
|
+
let pending = payload;
|
|
2670
|
+
let overdueAt = null;
|
|
2671
|
+
let disarm = null;
|
|
2672
|
+
const forget = () => {
|
|
2673
|
+
disarm?.();
|
|
2674
|
+
disarm = null;
|
|
2675
|
+
actor.timers.delete(timer);
|
|
2676
|
+
};
|
|
2677
|
+
const miss = (discarded) => {
|
|
2678
|
+
if (phase !== "fired")
|
|
2679
|
+
return;
|
|
2680
|
+
phase = hasOverdue.value ? "overdue" : "cancelled";
|
|
2681
|
+
forget();
|
|
2682
|
+
if (discarded && !hasOverdue.value)
|
|
2683
|
+
this.trace.writeStderr("actor.timer_discarded: the one-shot handler did not start\n", null);
|
|
2684
|
+
if (!hasOverdue.value || !actor.alive)
|
|
2685
|
+
return;
|
|
2686
|
+
const fault = {
|
|
2687
|
+
kind: "panic",
|
|
2688
|
+
timerOverdue: true,
|
|
2689
|
+
message: "actor timer did not start before its overdue deadline",
|
|
2690
|
+
};
|
|
2691
|
+
if (actor.active) {
|
|
2692
|
+
actor.active.pendingCancellation ??= fault;
|
|
2693
|
+
actor.active.cancel?.(actor.active.pendingCancellation);
|
|
2694
|
+
}
|
|
2695
|
+
else
|
|
2696
|
+
this.crashActor(actor, fault, handler);
|
|
2697
|
+
};
|
|
2698
|
+
const timer = {
|
|
2699
|
+
cancel: () => {
|
|
2700
|
+
if (phase !== "overdue")
|
|
2701
|
+
phase = "cancelled";
|
|
2702
|
+
forget();
|
|
2703
|
+
if (pending) {
|
|
2704
|
+
actor.disposals.push(pending);
|
|
2705
|
+
pending = null;
|
|
2706
|
+
this.dispatchActor(actor);
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
started: () => {
|
|
2710
|
+
if (phase === "fired" &&
|
|
2711
|
+
overdueAt !== null &&
|
|
2712
|
+
this.trace.virtualTimeMs >= overdueAt)
|
|
2713
|
+
miss(false);
|
|
2714
|
+
const allowed = phase !== "overdue";
|
|
2715
|
+
if (phase === "fired")
|
|
2716
|
+
phase = "started";
|
|
2717
|
+
forget();
|
|
2718
|
+
return allowed;
|
|
2719
|
+
},
|
|
2720
|
+
discarded: () => miss(true),
|
|
2721
|
+
};
|
|
2722
|
+
actor.timers.add(timer);
|
|
2723
|
+
disarm = this.scheduler.after(after.value, () => {
|
|
2724
|
+
disarm = null;
|
|
2725
|
+
if (phase !== "scheduled")
|
|
2726
|
+
return;
|
|
2727
|
+
phase = "fired";
|
|
2728
|
+
pending = null;
|
|
2729
|
+
if (hasOverdue.value) {
|
|
2730
|
+
overdueAt =
|
|
2731
|
+
this.trace.virtualTimeMs + Number(overdue.value) / 1_000_000;
|
|
2732
|
+
disarm = this.scheduler.after(overdue.value, () => miss(false));
|
|
2733
|
+
}
|
|
2734
|
+
const message = {
|
|
2735
|
+
timer,
|
|
2736
|
+
caller: { kind: "Timer" },
|
|
2737
|
+
handler,
|
|
2738
|
+
payload: payload.fields,
|
|
2739
|
+
reply: false,
|
|
2740
|
+
obligated: false,
|
|
2741
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
2742
|
+
};
|
|
2743
|
+
const status = this.admitActorMessage(actor, message);
|
|
2744
|
+
if (status === "full" || status === "closed") {
|
|
2745
|
+
actor.disposals.push(payload);
|
|
2746
|
+
timer.discarded();
|
|
2747
|
+
this.dispatchActor(actor);
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
this.completeShim(act, term, {
|
|
2751
|
+
kind: "timer",
|
|
2752
|
+
resource: operation.resource,
|
|
2753
|
+
cancel: () => timer.cancel(),
|
|
2754
|
+
closed: false,
|
|
2755
|
+
});
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
1988
2758
|
case "stream_start": {
|
|
1989
2759
|
const [target, payload] = args;
|
|
1990
2760
|
const fields = fieldsOf(payload);
|
|
@@ -2035,6 +2805,10 @@ class ExecutorV1 {
|
|
|
2035
2805
|
task.actorError = error;
|
|
2036
2806
|
this.settleTask(task, value ?? UNIT, closeFault ?? null);
|
|
2037
2807
|
return true;
|
|
2808
|
+
}, undefined, (actor) => {
|
|
2809
|
+
task.actorTarget = actor;
|
|
2810
|
+
for (const changed of [...task.changed])
|
|
2811
|
+
changed();
|
|
2038
2812
|
});
|
|
2039
2813
|
task.close = (done) => cancelRequest((fault) => {
|
|
2040
2814
|
this.settleTask(task, UNIT, fault);
|
|
@@ -2071,15 +2845,17 @@ class ExecutorV1 {
|
|
|
2071
2845
|
this.variant(shapes.success, discarded ? "DeliveryDiscarded" : "DeliveryAccepted"),
|
|
2072
2846
|
]);
|
|
2073
2847
|
const rejected = (reason) => this.variant(term.result_shape, "ResultErr", [
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2848
|
+
this.variant(term.error_shape, "ActorErrorRejected", [
|
|
2849
|
+
{
|
|
2850
|
+
kind: "record",
|
|
2851
|
+
typeId: this.aggregateName(shapes.failure),
|
|
2852
|
+
shape: shapes.failure,
|
|
2853
|
+
fields: [
|
|
2854
|
+
this.variant(shapes.reason, sendErrorRole(reason)),
|
|
2855
|
+
request,
|
|
2856
|
+
],
|
|
2857
|
+
},
|
|
2858
|
+
]),
|
|
2083
2859
|
]);
|
|
2084
2860
|
let parked = false;
|
|
2085
2861
|
const finish = (value) => {
|
|
@@ -2092,87 +2868,38 @@ class ExecutorV1 {
|
|
|
2092
2868
|
finish(rejected("Dead"));
|
|
2093
2869
|
return;
|
|
2094
2870
|
}
|
|
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
|
-
}
|
|
2871
|
+
const message = {
|
|
2872
|
+
caller,
|
|
2873
|
+
handler,
|
|
2874
|
+
payload: payload.fields,
|
|
2875
|
+
reply: false,
|
|
2876
|
+
obligated: false,
|
|
2877
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
2120
2878
|
};
|
|
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) {
|
|
2879
|
+
const status = this.admitActorMessage(actor, message);
|
|
2880
|
+
if (status === "full") {
|
|
2166
2881
|
if (operation.policy === "drop_newest") {
|
|
2167
|
-
|
|
2882
|
+
let settled = false;
|
|
2883
|
+
let waiting = false;
|
|
2884
|
+
this.closeValueAsync(payload, this.pipeFault(act), (fault) => {
|
|
2885
|
+
act.fault ??= fault;
|
|
2886
|
+
settled = true;
|
|
2887
|
+
if (waiting)
|
|
2888
|
+
finish(accepted(true));
|
|
2889
|
+
else
|
|
2890
|
+
this.completeShim(act, term, accepted(true));
|
|
2891
|
+
}, act.context.actor, act.rcOwner);
|
|
2892
|
+
if (!settled) {
|
|
2893
|
+
waiting = true;
|
|
2894
|
+
parked = true;
|
|
2895
|
+
this.running = false;
|
|
2896
|
+
}
|
|
2168
2897
|
return;
|
|
2169
2898
|
}
|
|
2170
2899
|
if (operation.policy === "reject") {
|
|
2171
2900
|
finish(rejected("Full"));
|
|
2172
2901
|
return;
|
|
2173
2902
|
}
|
|
2174
|
-
// The checker already resolved declaration defaults and explicit
|
|
2175
|
-
// overrides; the declaration's own resolution happened above.
|
|
2176
2903
|
if (operation.policy !== "wait")
|
|
2177
2904
|
throw new Error("submission has no admitted policy");
|
|
2178
2905
|
parked = true;
|
|
@@ -2180,20 +2907,10 @@ class ExecutorV1 {
|
|
|
2180
2907
|
actor.admission.push(submit);
|
|
2181
2908
|
return;
|
|
2182
2909
|
}
|
|
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));
|
|
2910
|
+
if (status === "closed")
|
|
2911
|
+
finish(rejected("Dead"));
|
|
2912
|
+
else
|
|
2913
|
+
finish(accepted(status === "discarded"));
|
|
2197
2914
|
};
|
|
2198
2915
|
submit();
|
|
2199
2916
|
return;
|
|
@@ -2202,15 +2919,32 @@ class ExecutorV1 {
|
|
|
2202
2919
|
const layout = this.pkg.supervisors?.find((layout) => layout.id === operation.supervisor);
|
|
2203
2920
|
if (!layout)
|
|
2204
2921
|
throw new Error("supervisor descriptor is missing");
|
|
2922
|
+
const isPool = layout.children.some((child) => child.is_pool);
|
|
2923
|
+
const count = isPool ? args[args.length - 1] : undefined;
|
|
2924
|
+
if (isPool && count?.kind !== "i64")
|
|
2925
|
+
throw new Error("pool construction has no count");
|
|
2926
|
+
const config = isPool ? args.slice(0, -1) : args;
|
|
2927
|
+
if (count?.kind === "i64" &&
|
|
2928
|
+
(count.value <= 0n || count.value > 2147483647n)) {
|
|
2929
|
+
const fault = {
|
|
2930
|
+
kind: "panic",
|
|
2931
|
+
message: "supervisor pool count must be between 1 and 2147483647",
|
|
2932
|
+
};
|
|
2933
|
+
this.running = false;
|
|
2934
|
+
this.closeValueAsync({ kind: "record", typeId: "", fields: config }, fault.message, (closeFault) => {
|
|
2935
|
+
this.raiseFault(act, combineFaults(fault, closeFault), term.unwind);
|
|
2936
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2937
|
+
}, act.context.actor, act.rcOwner);
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
const members = count?.kind === "i64" ? Number(count.value) : 1;
|
|
2205
2941
|
const owner = {
|
|
2206
2942
|
id: this.ids.supervisor(),
|
|
2207
2943
|
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)),
|
|
2944
|
+
config,
|
|
2945
|
+
children: layout.children.flatMap(() => Array.from({ length: members }, () => null)),
|
|
2946
|
+
specs: layout.children.flatMap((child) => Array.from({ length: members }, () => child)),
|
|
2947
|
+
offsets: layout.children.map((_child, index) => index),
|
|
2214
2948
|
recovering: new Set(),
|
|
2215
2949
|
restartBusy: false,
|
|
2216
2950
|
pendingRestarts: [],
|
|
@@ -2269,7 +3003,7 @@ class ExecutorV1 {
|
|
|
2269
3003
|
this.completeShim(act, term, {
|
|
2270
3004
|
kind: "vector",
|
|
2271
3005
|
elementType: "role",
|
|
2272
|
-
items: Array.from({ length:
|
|
3006
|
+
items: Array.from({ length: owner.children.length }, (_, index) => makeRole(offset + index)),
|
|
2273
3007
|
});
|
|
2274
3008
|
return;
|
|
2275
3009
|
}
|
|
@@ -2279,12 +3013,16 @@ class ExecutorV1 {
|
|
|
2279
3013
|
case "await_restarted": {
|
|
2280
3014
|
const target = args[0];
|
|
2281
3015
|
const role = "id" in target ? this.roles.get(target.id) : undefined;
|
|
2282
|
-
if (!role)
|
|
2283
|
-
|
|
2284
|
-
|
|
3016
|
+
if (!role) {
|
|
3017
|
+
if (target.kind !== "actor" && target.kind !== "supervisor")
|
|
3018
|
+
throw new Error("restarted requires an actor or supervisor handle");
|
|
3019
|
+
this.completeShim(act, term, UNIT);
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
if (this.roleRestartPending(role)) {
|
|
2285
3023
|
this.running = false;
|
|
2286
3024
|
const settled = () => {
|
|
2287
|
-
if (this.
|
|
3025
|
+
if (this.roleRestartPending(role)) {
|
|
2288
3026
|
role.waiting.push(settled);
|
|
2289
3027
|
return;
|
|
2290
3028
|
}
|
|
@@ -2324,7 +3062,10 @@ class ExecutorV1 {
|
|
|
2324
3062
|
if (target.kind === "actor") {
|
|
2325
3063
|
const actor = this.actorFor(target);
|
|
2326
3064
|
const complete = () => {
|
|
2327
|
-
|
|
3065
|
+
// A crash was reported when it happened and already counts toward
|
|
3066
|
+
// the exit status; only a fault raised while the actor was being
|
|
3067
|
+
// stopped passes to the caller that waits for it.
|
|
3068
|
+
const fault = actor.closingFault;
|
|
2328
3069
|
if (actor.closeFaultDebt !== undefined)
|
|
2329
3070
|
this.faultDebts.delete(actor.closeFaultDebt);
|
|
2330
3071
|
if (fault)
|
|
@@ -2370,6 +3111,14 @@ class ExecutorV1 {
|
|
|
2370
3111
|
throw new Error(`actor operation ${operation.op} has no executor`);
|
|
2371
3112
|
}
|
|
2372
3113
|
}
|
|
3114
|
+
scopeDeadline(act, expected) {
|
|
3115
|
+
for (let frame = act; frame?.context === act.context; frame = frame.caller) {
|
|
3116
|
+
for (const group of frame.scopes.values())
|
|
3117
|
+
if (group.deadline && (!expected || group.deadline === expected))
|
|
3118
|
+
return group.deadline;
|
|
3119
|
+
}
|
|
3120
|
+
return undefined;
|
|
3121
|
+
}
|
|
2373
3122
|
requestDeadline(act) {
|
|
2374
3123
|
let deadline = act.context.requestDeadline;
|
|
2375
3124
|
for (let frame = act; frame; frame = frame.caller) {
|
|
@@ -2380,11 +3129,12 @@ class ExecutorV1 {
|
|
|
2380
3129
|
}
|
|
2381
3130
|
return deadline;
|
|
2382
3131
|
}
|
|
2383
|
-
ask(protocol, inputs, complete, shapes) {
|
|
3132
|
+
ask(protocol, inputs, complete, shapes, selectedTarget) {
|
|
2384
3133
|
let settled = false;
|
|
2385
3134
|
let admitted = false;
|
|
2386
3135
|
let closing = false;
|
|
2387
3136
|
const owner = this.current.context.actor;
|
|
3137
|
+
const rcOwner = this.current.rcOwner;
|
|
2388
3138
|
const deadline = this.requestDeadline(this.current);
|
|
2389
3139
|
let cancelTimer;
|
|
2390
3140
|
let message;
|
|
@@ -2406,7 +3156,7 @@ class ExecutorV1 {
|
|
|
2406
3156
|
this.crashActor(owner, fault);
|
|
2407
3157
|
else if (fault)
|
|
2408
3158
|
this.rootFault ??= fault;
|
|
2409
|
-
}, admitted ? receiver : owner);
|
|
3159
|
+
}, admitted ? receiver : owner, admitted ? (receiver?.rcOwner ?? null) : rcOwner);
|
|
2410
3160
|
}
|
|
2411
3161
|
else
|
|
2412
3162
|
drained?.(null);
|
|
@@ -2430,7 +3180,7 @@ class ExecutorV1 {
|
|
|
2430
3180
|
closeFault = fault;
|
|
2431
3181
|
for (const waiter of closeWaiters.splice(0))
|
|
2432
3182
|
waiter(fault);
|
|
2433
|
-
}, owner);
|
|
3183
|
+
}, owner, rcOwner);
|
|
2434
3184
|
};
|
|
2435
3185
|
const reject = (reason) => {
|
|
2436
3186
|
if (settled || closing)
|
|
@@ -2463,6 +3213,9 @@ class ExecutorV1 {
|
|
|
2463
3213
|
const handler = actor.layout.handlers.find((handler) => handler.message_id === protocol.message);
|
|
2464
3214
|
if (!handler)
|
|
2465
3215
|
throw new Error(`actor ${actor.id} has no message ${protocol.message}`);
|
|
3216
|
+
selectedTarget?.(actor);
|
|
3217
|
+
if (settled || closing)
|
|
3218
|
+
return;
|
|
2466
3219
|
const full = actor.layout.mailbox_capacity !== undefined &&
|
|
2467
3220
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
2468
3221
|
if (full) {
|
|
@@ -2552,6 +3305,114 @@ class ExecutorV1 {
|
|
|
2552
3305
|
closeRequest(done);
|
|
2553
3306
|
};
|
|
2554
3307
|
}
|
|
3308
|
+
admitActorMessage(actor, message) {
|
|
3309
|
+
if (!actor.alive || actor.closing)
|
|
3310
|
+
return "closed";
|
|
3311
|
+
const full = actor.layout.mailbox_capacity !== undefined &&
|
|
3312
|
+
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3313
|
+
let declared = full ? actor.layout.overflow : null;
|
|
3314
|
+
const discard = (lost) => {
|
|
3315
|
+
actor.disposals.push({
|
|
3316
|
+
kind: "record",
|
|
3317
|
+
typeId: "",
|
|
3318
|
+
fields: lost.payload,
|
|
3319
|
+
});
|
|
3320
|
+
lost.timer?.discarded();
|
|
3321
|
+
this.dispatchActor(actor);
|
|
3322
|
+
};
|
|
3323
|
+
const coalesce = actor.layout.coalesce;
|
|
3324
|
+
if (declared === "coalesce" && coalesce) {
|
|
3325
|
+
const key = coalesce.keys.find((key) => key.message === message.handler.message_id);
|
|
3326
|
+
const queued = key
|
|
3327
|
+
? actor.mailbox.findIndex((queued) => !queued.obligated &&
|
|
3328
|
+
queued.handler.message_id === message.handler.message_id &&
|
|
3329
|
+
equals(queued.payload[key.param], message.payload[key.param]))
|
|
3330
|
+
: -1;
|
|
3331
|
+
if (queued >= 0) {
|
|
3332
|
+
const superseded = actor.mailbox[queued];
|
|
3333
|
+
actor.mailbox[queued] = message;
|
|
3334
|
+
discard(superseded);
|
|
3335
|
+
return "accepted";
|
|
3336
|
+
}
|
|
3337
|
+
declared = coalesce.fallback;
|
|
3338
|
+
}
|
|
3339
|
+
if (declared === "drop_new") {
|
|
3340
|
+
discard(message);
|
|
3341
|
+
return "discarded";
|
|
3342
|
+
}
|
|
3343
|
+
const oldest = declared === "drop_old"
|
|
3344
|
+
? actor.mailbox.findIndex((queued) => !queued.obligated)
|
|
3345
|
+
: -1;
|
|
3346
|
+
if (oldest >= 0) {
|
|
3347
|
+
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
3348
|
+
actor.mailbox.push(message);
|
|
3349
|
+
discard(evicted);
|
|
3350
|
+
return "accepted";
|
|
3351
|
+
}
|
|
3352
|
+
if (full)
|
|
3353
|
+
return "full";
|
|
3354
|
+
actor.mailbox.push(message);
|
|
3355
|
+
this.trace.snapshot("actor.send", {
|
|
3356
|
+
actor_id: actor.id,
|
|
3357
|
+
handler: message.handler.name,
|
|
3358
|
+
});
|
|
3359
|
+
this.dispatchActor(actor);
|
|
3360
|
+
return "accepted";
|
|
3361
|
+
}
|
|
3362
|
+
completeRoot() {
|
|
3363
|
+
this.rootComplete = true;
|
|
3364
|
+
// Quiesce actor timers before draining accepted work; sleeps still wake.
|
|
3365
|
+
for (const actor of this.actors.values())
|
|
3366
|
+
this.cancelActorTimers(actor);
|
|
3367
|
+
}
|
|
3368
|
+
startPeriodicTimers(actor) {
|
|
3369
|
+
if (this.rootComplete || actor.closing)
|
|
3370
|
+
return;
|
|
3371
|
+
for (const handler of actor.layout.handlers) {
|
|
3372
|
+
if (handler.every_ns == null)
|
|
3373
|
+
continue;
|
|
3374
|
+
const interval = Math.floor(handler.every_ns / 1_000_000);
|
|
3375
|
+
let next = this.trace.virtualTimeMs + interval;
|
|
3376
|
+
let disarm = null;
|
|
3377
|
+
const timer = {
|
|
3378
|
+
cancel: () => {
|
|
3379
|
+
disarm?.();
|
|
3380
|
+
disarm = null;
|
|
3381
|
+
actor.timers.delete(timer);
|
|
3382
|
+
},
|
|
3383
|
+
};
|
|
3384
|
+
const fire = () => {
|
|
3385
|
+
disarm = null;
|
|
3386
|
+
if (!actor.timers.has(timer))
|
|
3387
|
+
return;
|
|
3388
|
+
this.admitActorMessage(actor, {
|
|
3389
|
+
caller: { kind: "Timer" },
|
|
3390
|
+
handler,
|
|
3391
|
+
payload: [],
|
|
3392
|
+
reply: false,
|
|
3393
|
+
obligated: false,
|
|
3394
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
3395
|
+
});
|
|
3396
|
+
if (!actor.timers.has(timer))
|
|
3397
|
+
return;
|
|
3398
|
+
next += interval;
|
|
3399
|
+
if (next <= this.trace.virtualTimeMs)
|
|
3400
|
+
next +=
|
|
3401
|
+
(Math.floor((this.trace.virtualTimeMs - next) / interval) + 1) *
|
|
3402
|
+
interval;
|
|
3403
|
+
arm();
|
|
3404
|
+
};
|
|
3405
|
+
const arm = () => {
|
|
3406
|
+
disarm = this.scheduler.after(BigInt(Math.round((next - this.trace.virtualTimeMs) * 1_000_000)), fire);
|
|
3407
|
+
};
|
|
3408
|
+
actor.timers.add(timer);
|
|
3409
|
+
arm();
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
cancelActorTimers(actor) {
|
|
3413
|
+
for (const timer of [...actor.timers])
|
|
3414
|
+
timer.cancel();
|
|
3415
|
+
}
|
|
2555
3416
|
dispatchActor(actor) {
|
|
2556
3417
|
if (actor.busy || !actor.alive || actor.drainingMailbox)
|
|
2557
3418
|
return;
|
|
@@ -2587,13 +3448,21 @@ class ExecutorV1 {
|
|
|
2587
3448
|
this.dispatchActor(actor);
|
|
2588
3449
|
return;
|
|
2589
3450
|
}
|
|
2590
|
-
const message = actor.mailbox
|
|
3451
|
+
const message = actor.mailbox[0];
|
|
2591
3452
|
if (!message || !actor.alive) {
|
|
2592
3453
|
actor.busy = false;
|
|
2593
3454
|
if (actor.closing && actor.alive)
|
|
2594
3455
|
this.dispatchActor(actor);
|
|
2595
3456
|
return;
|
|
2596
3457
|
}
|
|
3458
|
+
// An overdue claim can synchronously begin terminal cleanup. Keep the
|
|
3459
|
+
// payload in its mailbox until that claim succeeds.
|
|
3460
|
+
if (message.timer && !message.timer.started()) {
|
|
3461
|
+
actor.busy = false;
|
|
3462
|
+
this.dispatchActor(actor);
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
actor.mailbox.shift();
|
|
2597
3466
|
for (const admit of actor.admission.splice(0))
|
|
2598
3467
|
admit();
|
|
2599
3468
|
if (message.requestDeadline !== undefined &&
|
|
@@ -2623,7 +3492,7 @@ class ExecutorV1 {
|
|
|
2623
3492
|
const drained = (fault) => {
|
|
2624
3493
|
actor.busy = false;
|
|
2625
3494
|
if (fault)
|
|
2626
|
-
this.crashActor(actor, fault);
|
|
3495
|
+
this.crashActor(actor, fault, message.handler);
|
|
2627
3496
|
else
|
|
2628
3497
|
this.dispatchActor(actor);
|
|
2629
3498
|
};
|
|
@@ -2639,8 +3508,9 @@ class ExecutorV1 {
|
|
|
2639
3508
|
else {
|
|
2640
3509
|
this.crashActor(actor, {
|
|
2641
3510
|
kind: "panic",
|
|
3511
|
+
handlerFailed: true,
|
|
2642
3512
|
message: "unhandled declared handler failure",
|
|
2643
|
-
});
|
|
3513
|
+
}, message.handler);
|
|
2644
3514
|
return;
|
|
2645
3515
|
}
|
|
2646
3516
|
}
|
|
@@ -2667,7 +3537,7 @@ class ExecutorV1 {
|
|
|
2667
3537
|
this.dispatchActor(actor);
|
|
2668
3538
|
}
|
|
2669
3539
|
else {
|
|
2670
|
-
this.crashActor(actor, fault);
|
|
3540
|
+
this.crashActor(actor, fault, message.handler);
|
|
2671
3541
|
message.complete(null, "Trapped");
|
|
2672
3542
|
}
|
|
2673
3543
|
},
|
|
@@ -2676,7 +3546,12 @@ class ExecutorV1 {
|
|
|
2676
3546
|
this.runFrame(frame);
|
|
2677
3547
|
});
|
|
2678
3548
|
}
|
|
2679
|
-
crashActor(actor, fault) {
|
|
3549
|
+
crashActor(actor, fault, handler) {
|
|
3550
|
+
this.reportFault(fault);
|
|
3551
|
+
if (handler) {
|
|
3552
|
+
const context = `${actor.layout.name}.${handler.name}`;
|
|
3553
|
+
this.trace.writeStderr(`hew: actor crash in ${context} (actor ${actor.identity}): ${faultInfo(fault)[1]}\n`, null);
|
|
3554
|
+
}
|
|
2680
3555
|
const debt = ++this.nextFaultDebt;
|
|
2681
3556
|
this.faultDebts.add(debt);
|
|
2682
3557
|
if (actor.supervisor)
|
|
@@ -2684,6 +3559,7 @@ class ExecutorV1 {
|
|
|
2684
3559
|
actor.crashing ??= fault;
|
|
2685
3560
|
actor.alive = false;
|
|
2686
3561
|
actor.busy = false;
|
|
3562
|
+
this.cancelActorTimers(actor);
|
|
2687
3563
|
this.trace.snapshot("actor.crash", {
|
|
2688
3564
|
actor_id: actor.id,
|
|
2689
3565
|
message: this.faultText(fault),
|
|
@@ -2693,47 +3569,114 @@ class ExecutorV1 {
|
|
|
2693
3569
|
actor.mailboxDrained.push(() => finished(action));
|
|
2694
3570
|
return;
|
|
2695
3571
|
}
|
|
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
|
-
|
|
3572
|
+
this.closeValueAsync(actor.state, this.faultText(fault), (stateFault) => {
|
|
3573
|
+
actor.closingFault = combineFaults(actor.closingFault, stateFault);
|
|
3574
|
+
fault = combineFaults(fault, stateFault);
|
|
3575
|
+
actor.crashing = fault;
|
|
3576
|
+
this.reportFault(fault);
|
|
3577
|
+
this.drainMailbox(actor, this.faultText(fault), (payloadFault) => {
|
|
3578
|
+
actor.closingFault = combineFaults(actor.closingFault, payloadFault);
|
|
3579
|
+
fault = combineFaults(fault, payloadFault);
|
|
3580
|
+
actor.crashing = fault;
|
|
3581
|
+
this.reportFault(fault);
|
|
3582
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
3583
|
+
actor.closingFault = combineFaults(actor.closingFault, sweepFault);
|
|
3584
|
+
actor.crashing = combineFaults(actor.crashing, sweepFault);
|
|
3585
|
+
this.reportFault(actor.crashing);
|
|
3586
|
+
if (actor.closingFault) {
|
|
3587
|
+
this.reportFault(actor.closingFault);
|
|
3588
|
+
}
|
|
3589
|
+
actor.completed = true;
|
|
3590
|
+
for (const wake of actor.closed.splice(0))
|
|
3591
|
+
wake();
|
|
3592
|
+
for (const admit of actor.admission.splice(0))
|
|
3593
|
+
admit();
|
|
3594
|
+
if (actor.supervisor) {
|
|
3595
|
+
const { owner, child } = actor.supervisor;
|
|
3596
|
+
if (action === "Restart")
|
|
3597
|
+
this.restartChild(owner, child, [debt]);
|
|
3598
|
+
else if (action === "Escalate")
|
|
3599
|
+
this.escalateSupervisor(owner, [debt]);
|
|
3600
|
+
else {
|
|
3601
|
+
owner.spent.add(child);
|
|
3602
|
+
owner.recovering.delete(child);
|
|
3603
|
+
this.wakeRoles();
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
});
|
|
3607
|
+
});
|
|
3608
|
+
}, actor);
|
|
2715
3609
|
};
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3610
|
+
const runHook = () => {
|
|
3611
|
+
if (actor.layout.crash !== undefined) {
|
|
3612
|
+
const [code, message] = faultInfo(fault);
|
|
3613
|
+
const info = {
|
|
3614
|
+
kind: "record",
|
|
3615
|
+
typeId: this.aggregateName(actor.layout.crash_info),
|
|
3616
|
+
shape: actor.layout.crash_info,
|
|
3617
|
+
fields: [
|
|
3618
|
+
{
|
|
3619
|
+
kind: "enum",
|
|
3620
|
+
typeId: this.variantName(actor.layout.crash_kind),
|
|
3621
|
+
shape: actor.layout.crash_kind,
|
|
3622
|
+
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 214
|
|
3623
|
+
? "CrashKindTimerOverdue"
|
|
3624
|
+
: code === 213
|
|
3625
|
+
? "CrashKindHandlerFailed"
|
|
3626
|
+
: "CrashKindFault"),
|
|
3627
|
+
payload: [],
|
|
3628
|
+
},
|
|
3629
|
+
stringValue(String(message)),
|
|
3630
|
+
],
|
|
3631
|
+
};
|
|
3632
|
+
let cancelTimer;
|
|
3633
|
+
const complete = (action) => {
|
|
3634
|
+
cancelTimer?.();
|
|
3635
|
+
actor.terminalHook = null;
|
|
3636
|
+
finished(action);
|
|
3637
|
+
};
|
|
3638
|
+
const hook = this.invokeFrame(actor, actor.layout.crash, [actor.state, info], (value) => {
|
|
3639
|
+
const shape = this.pkg.variants[actor.layout.crash_action];
|
|
3640
|
+
const action = value.kind === "enum" ? shape?.cases[value.tag]?.name : undefined;
|
|
3641
|
+
if (!action)
|
|
3642
|
+
throw new Error("crash hook returned no declared action");
|
|
3643
|
+
complete(action);
|
|
3644
|
+
}, (hookFault) => {
|
|
3645
|
+
actor.closingFault = combineFaults(actor.closingFault, hookFault);
|
|
3646
|
+
fault = combineFaults(fault, hookFault);
|
|
3647
|
+
actor.crashing = fault;
|
|
3648
|
+
this.reportFault(fault);
|
|
3649
|
+
complete("Restart");
|
|
3650
|
+
});
|
|
3651
|
+
actor.terminalHook = hook;
|
|
3652
|
+
const parent = actor.supervisor;
|
|
3653
|
+
const stopNs = parent
|
|
3654
|
+
? this.childStopBudget(parent.owner, parent.child)
|
|
3655
|
+
: DEFAULT_ACTOR_STOP_NS;
|
|
3656
|
+
hook.requestDeadline = this.trace.virtualTimeMs + stopNs / 1_000_000;
|
|
3657
|
+
const expire = () => {
|
|
3658
|
+
hook.pendingCancellation ??= {
|
|
3659
|
+
kind: "panic",
|
|
3660
|
+
message: "crash hook deadline elapsed",
|
|
3661
|
+
deadline: true,
|
|
3662
|
+
};
|
|
3663
|
+
hook.cancel?.(hook.pendingCancellation);
|
|
3664
|
+
};
|
|
3665
|
+
if (stopNs <= 0)
|
|
3666
|
+
expire();
|
|
3667
|
+
else
|
|
3668
|
+
cancelTimer = this.scheduler.after(BigInt(stopNs), expire);
|
|
3669
|
+
}
|
|
3670
|
+
else
|
|
3671
|
+
finished("Restart");
|
|
3672
|
+
};
|
|
3673
|
+
this.drainMailbox(actor, this.faultText(fault), (cleanupFault) => {
|
|
3674
|
+
actor.closingFault = combineFaults(actor.closingFault, cleanupFault);
|
|
3675
|
+
fault = combineFaults(fault, cleanupFault);
|
|
3676
|
+
actor.crashing = fault;
|
|
3677
|
+
this.reportFault(fault);
|
|
3678
|
+
runHook();
|
|
3679
|
+
});
|
|
2737
3680
|
}
|
|
2738
3681
|
supervisorFor(value) {
|
|
2739
3682
|
if (value.kind !== "supervisor")
|
|
@@ -2786,6 +3729,23 @@ class ExecutorV1 {
|
|
|
2786
3729
|
return (!!owner?.alive &&
|
|
2787
3730
|
(owner.recovering.has(role.child) || owner.children[role.child] === null));
|
|
2788
3731
|
}
|
|
3732
|
+
roleRestartPending(role) {
|
|
3733
|
+
const parent = this.roles.get(role.ownerId);
|
|
3734
|
+
if (parent && this.roleRestartPending(parent))
|
|
3735
|
+
return true;
|
|
3736
|
+
if (this.rolePending(role))
|
|
3737
|
+
return true;
|
|
3738
|
+
const value = this.roleValue(role) ?? role.latest;
|
|
3739
|
+
if (value?.kind === "actor") {
|
|
3740
|
+
const actor = this.actorFor(value);
|
|
3741
|
+
return !actor.completed && (actor.closing || !actor.alive);
|
|
3742
|
+
}
|
|
3743
|
+
if (value?.kind === "supervisor") {
|
|
3744
|
+
const owner = this.supervisorFor(value);
|
|
3745
|
+
return !owner.completed && !owner.alive;
|
|
3746
|
+
}
|
|
3747
|
+
return false;
|
|
3748
|
+
}
|
|
2789
3749
|
wakeRoles() {
|
|
2790
3750
|
for (const role of this.roles.values()) {
|
|
2791
3751
|
this.roleValue(role);
|
|
@@ -2918,6 +3878,16 @@ class ExecutorV1 {
|
|
|
2918
3878
|
else if (value.kind === "supervisor")
|
|
2919
3879
|
this.stopSupervisor(this.supervisorFor(value), true);
|
|
2920
3880
|
}
|
|
3881
|
+
childStopBudget(owner, child) {
|
|
3882
|
+
const spec = owner.specs[child];
|
|
3883
|
+
if (spec.stop_config !== undefined) {
|
|
3884
|
+
const duration = owner.config[spec.stop_config];
|
|
3885
|
+
if (duration?.kind !== "i64" || duration.value < 0n)
|
|
3886
|
+
throw new Error("child stop budget is not a non-negative duration");
|
|
3887
|
+
return Number(duration.value);
|
|
3888
|
+
}
|
|
3889
|
+
return (spec.stop_ns ?? ("actor" in spec.role ? DEFAULT_ACTOR_STOP_NS : null));
|
|
3890
|
+
}
|
|
2921
3891
|
stopSupervisor(owner, force = false) {
|
|
2922
3892
|
if (force) {
|
|
2923
3893
|
owner.terminating = true;
|
|
@@ -2937,12 +3907,7 @@ class ExecutorV1 {
|
|
|
2937
3907
|
const index = childIndex--;
|
|
2938
3908
|
const child = owner.children[index];
|
|
2939
3909
|
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;
|
|
3910
|
+
const stopNs = this.childStopBudget(owner, index);
|
|
2946
3911
|
if (!owner.terminating && stopNs !== null && stopNs > 0) {
|
|
2947
3912
|
owner.teardownCancelTimer = this.scheduler.after(BigInt(stopNs), () => this.terminateChild(child));
|
|
2948
3913
|
}
|
|
@@ -2950,9 +3915,11 @@ class ExecutorV1 {
|
|
|
2950
3915
|
return;
|
|
2951
3916
|
}
|
|
2952
3917
|
this.closeValueAsync({ kind: "record", typeId: "", fields: owner.config }, null, (fault) => {
|
|
2953
|
-
|
|
2954
|
-
|
|
3918
|
+
const debt = fault ? ++this.nextFaultDebt : undefined;
|
|
3919
|
+
if (debt !== undefined)
|
|
3920
|
+
this.faultDebts.add(debt);
|
|
2955
3921
|
owner.completed = true;
|
|
3922
|
+
this.settleSupervisedEnd(owner, fault ? "Crashed" : owner.terminating ? "Terminated" : "Stopped", debt === undefined ? [] : [debt]);
|
|
2956
3923
|
for (const wake of owner.closed.splice(0))
|
|
2957
3924
|
wake();
|
|
2958
3925
|
this.wakeRoles();
|
|
@@ -2987,6 +3954,15 @@ class ExecutorV1 {
|
|
|
2987
3954
|
if (actor.closing || actor.completed)
|
|
2988
3955
|
return;
|
|
2989
3956
|
actor.closing = true;
|
|
3957
|
+
this.cancelActorTimers(actor);
|
|
3958
|
+
if (actor.constructing && actor.active) {
|
|
3959
|
+
actor.active.pendingCancellation ??= {
|
|
3960
|
+
kind: "panic",
|
|
3961
|
+
message: "actor construction stopped",
|
|
3962
|
+
cancelled: true,
|
|
3963
|
+
};
|
|
3964
|
+
actor.active.cancel?.(actor.active.pendingCancellation);
|
|
3965
|
+
}
|
|
2990
3966
|
// Admission closes immediately. The active turn and every accepted queued
|
|
2991
3967
|
// turn finish before stop hooks and state release.
|
|
2992
3968
|
this.dispatchActor(actor);
|
|
@@ -2998,8 +3974,17 @@ class ExecutorV1 {
|
|
|
2998
3974
|
return;
|
|
2999
3975
|
actor.closing = true;
|
|
3000
3976
|
actor.terminating = true;
|
|
3001
|
-
|
|
3002
|
-
actor.
|
|
3977
|
+
this.cancelActorTimers(actor);
|
|
3978
|
+
for (const context of [actor.active, actor.terminalHook]) {
|
|
3979
|
+
if (!context)
|
|
3980
|
+
continue;
|
|
3981
|
+
context.pendingCancellation ??= {
|
|
3982
|
+
kind: "panic",
|
|
3983
|
+
message: "actor terminated",
|
|
3984
|
+
cancelled: true,
|
|
3985
|
+
};
|
|
3986
|
+
context.cancel?.(context.pendingCancellation);
|
|
3987
|
+
}
|
|
3003
3988
|
this.dispatchActor(actor);
|
|
3004
3989
|
for (const admit of actor.admission.splice(0))
|
|
3005
3990
|
admit();
|
|
@@ -3010,11 +3995,11 @@ class ExecutorV1 {
|
|
|
3010
3995
|
const next = () => {
|
|
3011
3996
|
const hook = actor.terminating ? undefined : hooks.shift();
|
|
3012
3997
|
if (hook !== undefined) {
|
|
3013
|
-
actor.
|
|
3014
|
-
actor.
|
|
3998
|
+
actor.terminalHook = this.invokeFrame(actor, hook, [actor.state], () => {
|
|
3999
|
+
actor.terminalHook = null;
|
|
3015
4000
|
next();
|
|
3016
4001
|
}, (fault) => {
|
|
3017
|
-
actor.
|
|
4002
|
+
actor.terminalHook = null;
|
|
3018
4003
|
if (!(actor.terminating && fault.kind === "panic" && fault.cancelled))
|
|
3019
4004
|
actor.closingFault = combineFaults(actor.closingFault, fault);
|
|
3020
4005
|
hooks.length = 0;
|
|
@@ -3024,31 +4009,37 @@ class ExecutorV1 {
|
|
|
3024
4009
|
else {
|
|
3025
4010
|
this.closeValueAsync(actor.state, this.faultText(actor.closingFault), (fault) => {
|
|
3026
4011
|
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
|
-
|
|
4012
|
+
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
4013
|
+
actor.closingFault = combineFaults(actor.closingFault, sweepFault);
|
|
4014
|
+
// Stopped cleanup belongs to the completion barrier, independently
|
|
4015
|
+
// of handler-crash debt and supervisor restart decisions.
|
|
4016
|
+
actor.crashing = null;
|
|
4017
|
+
if (actor.closingFault) {
|
|
4018
|
+
this.reportFault(actor.closingFault);
|
|
4019
|
+
actor.closeFaultDebt = ++this.nextFaultDebt;
|
|
4020
|
+
this.faultDebts.add(actor.closeFaultDebt);
|
|
4021
|
+
}
|
|
4022
|
+
actor.completed = true;
|
|
4023
|
+
actor.alive = false;
|
|
4024
|
+
actor.busy = false;
|
|
4025
|
+
this.trace.snapshot(actor.terminating ? "actor.terminate" : "actor.stop", { actor_id: actor.id });
|
|
4026
|
+
this.settleSupervisedEnd(actor, actor.closingFault
|
|
4027
|
+
? "Crashed"
|
|
4028
|
+
: actor.terminating
|
|
4029
|
+
? "Terminated"
|
|
4030
|
+
: "Stopped", actor.closeFaultDebt === undefined
|
|
4031
|
+
? []
|
|
4032
|
+
: [actor.closeFaultDebt]);
|
|
4033
|
+
for (const wake of actor.closed.splice(0))
|
|
4034
|
+
wake();
|
|
4035
|
+
});
|
|
3045
4036
|
}, actor);
|
|
3046
4037
|
}
|
|
3047
4038
|
};
|
|
3048
4039
|
next();
|
|
3049
4040
|
}
|
|
3050
|
-
settleSupervisedEnd(
|
|
3051
|
-
const parent =
|
|
4041
|
+
settleSupervisedEnd(subject, reason, debts) {
|
|
4042
|
+
const parent = subject.supervisor;
|
|
3052
4043
|
if (!parent ||
|
|
3053
4044
|
!parent.owner.alive ||
|
|
3054
4045
|
parent.owner.recovering.has(parent.child))
|
|
@@ -3057,7 +4048,7 @@ class ExecutorV1 {
|
|
|
3057
4048
|
const policy = owner.specs[child].restart;
|
|
3058
4049
|
if (policy === "permanent" ||
|
|
3059
4050
|
(policy === "transient" && reason !== "Stopped")) {
|
|
3060
|
-
this.restartChild(owner, child,
|
|
4051
|
+
this.restartChild(owner, child, debts);
|
|
3061
4052
|
}
|
|
3062
4053
|
else {
|
|
3063
4054
|
owner.spent.add(child);
|
|
@@ -3065,6 +4056,16 @@ class ExecutorV1 {
|
|
|
3065
4056
|
}
|
|
3066
4057
|
}
|
|
3067
4058
|
// ── exit and faults ──────────────────────────────────────────────────────
|
|
4059
|
+
reportFault(fault) {
|
|
4060
|
+
const diagnostics = [fault.primary ?? fault, ...(fault.secondary ?? [])];
|
|
4061
|
+
diagnostics.forEach((diagnostic, index) => {
|
|
4062
|
+
const origin = diagnostic.primary ?? diagnostic;
|
|
4063
|
+
if (origin.reported)
|
|
4064
|
+
return;
|
|
4065
|
+
this.trace.writeStderr(faultDiagnostic(origin, index !== 0), null);
|
|
4066
|
+
origin.reported = true;
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
3068
4069
|
haltWithFault(fault) {
|
|
3069
4070
|
const isPanic = fault.kind === "panic";
|
|
3070
4071
|
const status = isPanic ? "panic" : "trap";
|
|
@@ -3074,7 +4075,7 @@ class ExecutorV1 {
|
|
|
3074
4075
|
: (fault.message ?? trapMessage(fault.trap));
|
|
3075
4076
|
const [code, kind] = faultInfo(fault);
|
|
3076
4077
|
// Native writes the failure line to stderr before it exits; so does this.
|
|
3077
|
-
this.
|
|
4078
|
+
this.reportFault(fault);
|
|
3078
4079
|
// A failing run reports no exit code: native exits 1 for every fault and
|
|
3079
4080
|
// names the kind in its message, so the kind is what travels, in
|
|
3080
4081
|
// `runtime_failures`. The page turns it into an exit code of its own.
|
|
@@ -3114,6 +4115,12 @@ function combineFaults(primary, secondary) {
|
|
|
3114
4115
|
}
|
|
3115
4116
|
function faultInfo(fault) {
|
|
3116
4117
|
if (fault.kind === "panic") {
|
|
4118
|
+
if (fault.handlerFailed)
|
|
4119
|
+
return [213, "ActorUnhandledFailure"];
|
|
4120
|
+
if (fault.timerOverdue)
|
|
4121
|
+
return [214, "ActorTimerOverdue"];
|
|
4122
|
+
if (fault.actorSendFailed)
|
|
4123
|
+
return [206, "ActorSendFailed"];
|
|
3117
4124
|
if (fault.cancelled)
|
|
3118
4125
|
return [-1, "Cancelled"];
|
|
3119
4126
|
if (fault.deadline)
|
|
@@ -3128,11 +4135,18 @@ function faultInfo(fault) {
|
|
|
3128
4135
|
actor_refused: [-4, "Refused"],
|
|
3129
4136
|
}[fault.trap];
|
|
3130
4137
|
}
|
|
3131
|
-
function
|
|
4138
|
+
function faultDiagnostic(fault, secondary = false) {
|
|
3132
4139
|
const [code, reason] = faultInfo(fault);
|
|
3133
4140
|
const label = secondary ? "secondary failure" : "failure";
|
|
3134
|
-
const message = fault.message === undefined
|
|
3135
|
-
|
|
4141
|
+
const message = fault.message === undefined ||
|
|
4142
|
+
(fault.kind === "panic" &&
|
|
4143
|
+
(fault.cancelled || fault.deadline || fault.timerOverdue))
|
|
4144
|
+
? ""
|
|
4145
|
+
: `: ${fault.message}`;
|
|
4146
|
+
return `hew: ${label}: ${reason} (${code})${message}\n`;
|
|
4147
|
+
}
|
|
4148
|
+
function faultReport(fault, secondary = false) {
|
|
4149
|
+
return (faultDiagnostic(fault, secondary) +
|
|
3136
4150
|
(fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
|
|
3137
4151
|
}
|
|
3138
4152
|
// ── refs ────────────────────────────────────────────────────────────────────
|
|
@@ -3286,7 +4300,9 @@ function toComparable(value) {
|
|
|
3286
4300
|
case "vector":
|
|
3287
4301
|
return value.items.map(toComparable);
|
|
3288
4302
|
case "rc":
|
|
3289
|
-
return
|
|
4303
|
+
return "Rc";
|
|
4304
|
+
case "weak":
|
|
4305
|
+
return "Weak";
|
|
3290
4306
|
case "map":
|
|
3291
4307
|
return [...value.entries.values()].map((entry) => [
|
|
3292
4308
|
toComparable(entry.key),
|
|
@@ -3459,7 +4475,7 @@ function applyBinary(op, lhs, rhs, ty) {
|
|
|
3459
4475
|
};
|
|
3460
4476
|
}
|
|
3461
4477
|
if (lhs.kind === "string") {
|
|
3462
|
-
return
|
|
4478
|
+
return stringValue(lhs.value + asString(rhs));
|
|
3463
4479
|
}
|
|
3464
4480
|
return {
|
|
3465
4481
|
kind: "i64",
|
|
@@ -3525,7 +4541,7 @@ function applyCast(value, to) {
|
|
|
3525
4541
|
return { kind: "bool", value: truth(value) };
|
|
3526
4542
|
case "char":
|
|
3527
4543
|
case "string":
|
|
3528
|
-
return
|
|
4544
|
+
return stringValue(asString(value));
|
|
3529
4545
|
default:
|
|
3530
4546
|
break;
|
|
3531
4547
|
}
|