@hew-lang/sandbox-vm 0.6.0 → 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/run-program.js +1 -0
- package/dist/interpreter/strings.d.ts +21 -0
- package/dist/interpreter/strings.js +191 -0
- package/dist/interpreter/types.d.ts +3 -1
- 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 +1489 -332
- package/dist/interpreter/v1/package.d.ts +46 -6
- 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 +243 -42
- 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 +58 -23
- package/dist/interpreter/values.d.ts +32 -9
- package/dist/interpreter/values.js +42 -8
- 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,12 +95,16 @@ 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;
|
|
107
|
+
actorIdentity = 0n;
|
|
101
108
|
faultDebts = new Set();
|
|
102
109
|
nextFaultDebt = 0;
|
|
103
110
|
closingResources = new WeakSet();
|
|
@@ -113,7 +120,35 @@ class ExecutorV1 {
|
|
|
113
120
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
114
121
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
115
122
|
this.host = {
|
|
123
|
+
argv: [...(options.argv ?? ["main.hew"])],
|
|
116
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
|
+
},
|
|
117
152
|
newPipe: (capacity) => this.newPipe(capacity),
|
|
118
153
|
closePipe: (value) => this.pipes.close(value, this.pipeFault(this.current)),
|
|
119
154
|
releaseValue: (value) => this.closeValue(value, this.pipeFault(this.current)),
|
|
@@ -134,7 +169,7 @@ class ExecutorV1 {
|
|
|
134
169
|
id: "actor:root",
|
|
135
170
|
actor: null,
|
|
136
171
|
returned: (value) => {
|
|
137
|
-
this.
|
|
172
|
+
this.completeRoot();
|
|
138
173
|
this.trace.exitCode =
|
|
139
174
|
pkg.entry?.exit === "status" && value.kind === "i64"
|
|
140
175
|
? Number(value.value)
|
|
@@ -142,7 +177,7 @@ class ExecutorV1 {
|
|
|
142
177
|
},
|
|
143
178
|
failed: (fault) => {
|
|
144
179
|
this.rootFault = fault;
|
|
145
|
-
this.
|
|
180
|
+
this.completeRoot();
|
|
146
181
|
},
|
|
147
182
|
});
|
|
148
183
|
}
|
|
@@ -172,6 +207,10 @@ class ExecutorV1 {
|
|
|
172
207
|
this.running = true;
|
|
173
208
|
while (this.running) {
|
|
174
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
|
+
};
|
|
175
214
|
while (act.opIndex < act.block.ops.length) {
|
|
176
215
|
const op = act.block.ops[act.opIndex++];
|
|
177
216
|
if (op.op !== "coverage.hit")
|
|
@@ -202,9 +241,19 @@ class ExecutorV1 {
|
|
|
202
241
|
}
|
|
203
242
|
// ── frames ───────────────────────────────────────────────────────────────
|
|
204
243
|
activate(fn, args, caller, result, normal, unwind, context) {
|
|
205
|
-
|
|
206
|
-
|
|
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])
|
|
207
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;
|
|
208
257
|
const env = new Map();
|
|
209
258
|
for (const [at, param] of fn.params.entries()) {
|
|
210
259
|
env.set(param.value, ownedRef(args[at] ?? UNIT));
|
|
@@ -230,7 +279,13 @@ class ExecutorV1 {
|
|
|
230
279
|
arm.fields.forEach(record);
|
|
231
280
|
}
|
|
232
281
|
return {
|
|
233
|
-
|
|
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,
|
|
234
289
|
fn,
|
|
235
290
|
block: this.blockAt(fn, fn.entry),
|
|
236
291
|
opIndex: 0,
|
|
@@ -372,7 +427,7 @@ class ExecutorV1 {
|
|
|
372
427
|
this.define(act, op.dst, { kind: "f64", value: floatConst(op) });
|
|
373
428
|
return;
|
|
374
429
|
case "const.char":
|
|
375
|
-
this.define(act, op.dst,
|
|
430
|
+
this.define(act, op.dst, stringValue(op.value));
|
|
376
431
|
return;
|
|
377
432
|
case "const.unit":
|
|
378
433
|
this.define(act, op.dst, UNIT);
|
|
@@ -381,10 +436,7 @@ class ExecutorV1 {
|
|
|
381
436
|
this.define(act, op.dst, { kind: "i64", value: BigInt(op.nanos) });
|
|
382
437
|
return;
|
|
383
438
|
case "const.str":
|
|
384
|
-
this.define(act, op.dst,
|
|
385
|
-
kind: "string",
|
|
386
|
-
value: this.pkg.strings[op.str] ?? "",
|
|
387
|
-
});
|
|
439
|
+
this.define(act, op.dst, stringValue(this.pkg.strings[op.str] ?? ""));
|
|
388
440
|
return;
|
|
389
441
|
case "const.bytes":
|
|
390
442
|
this.define(act, op.dst, bytesValue(this.pkg.bytes[op.bytes] ?? []));
|
|
@@ -550,7 +602,8 @@ class ExecutorV1 {
|
|
|
550
602
|
const shape = this.closureAt(op.closure);
|
|
551
603
|
this.define(act, op.dst, {
|
|
552
604
|
kind: "closure",
|
|
553
|
-
|
|
605
|
+
entries: shape.entries,
|
|
606
|
+
specialization: null,
|
|
554
607
|
environment: {
|
|
555
608
|
kind: "record",
|
|
556
609
|
typeId: "",
|
|
@@ -559,12 +612,23 @@ class ExecutorV1 {
|
|
|
559
612
|
});
|
|
560
613
|
return;
|
|
561
614
|
}
|
|
562
|
-
case "callable.coerce":
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
615
|
+
case "callable.coerce": {
|
|
616
|
+
const callable = this.read(act, op.source);
|
|
617
|
+
if (op.specialization === null) {
|
|
618
|
+
this.define(act, op.dst, callable);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
if (callable.kind !== "closure" ||
|
|
622
|
+
!callable.entries.some((entry) => entry.specialization === op.specialization)) {
|
|
623
|
+
throw new Error("callable specialization has no checked invocation entry");
|
|
624
|
+
}
|
|
625
|
+
this.define(act, op.dst, {
|
|
626
|
+
...callable,
|
|
627
|
+
specialization: op.specialization,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
567
630
|
return;
|
|
631
|
+
}
|
|
568
632
|
case "dyn.make":
|
|
569
633
|
this.define(act, op.dst, {
|
|
570
634
|
kind: "dyn",
|
|
@@ -595,6 +659,9 @@ class ExecutorV1 {
|
|
|
595
659
|
const duration = this.read(act, op.duration);
|
|
596
660
|
if (duration.kind !== "i64")
|
|
597
661
|
throw new Error("scope duration is not a duration");
|
|
662
|
+
group.expiresAt =
|
|
663
|
+
this.trace.virtualTimeMs +
|
|
664
|
+
Math.max(0, Number(duration.value) / 1_000_000);
|
|
598
665
|
group.cancelTimer = this.scheduler.after(duration.value, () => {
|
|
599
666
|
group.deadline = {
|
|
600
667
|
kind: "panic",
|
|
@@ -636,7 +703,9 @@ class ExecutorV1 {
|
|
|
636
703
|
const context = {
|
|
637
704
|
id,
|
|
638
705
|
actor: act.context.actor,
|
|
639
|
-
|
|
706
|
+
rcOwner: act.rcOwner,
|
|
707
|
+
requestDeadline: this.requestDeadline(act),
|
|
708
|
+
returned: (value) => this.closeValueAsync(value, null, (fault) => this.finishGenerator(generator, fault), act.context.actor, act.rcOwner),
|
|
640
709
|
failed: (fault) => this.finishGenerator(generator, fault),
|
|
641
710
|
};
|
|
642
711
|
const generator = {
|
|
@@ -731,10 +800,10 @@ class ExecutorV1 {
|
|
|
731
800
|
changed();
|
|
732
801
|
};
|
|
733
802
|
if (failure && winner)
|
|
734
|
-
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor);
|
|
803
|
+
this.closeValueAsync(value, this.faultText(failure), finish, act.context.actor, act.rcOwner);
|
|
735
804
|
else
|
|
736
805
|
finish();
|
|
737
|
-
}, act.context.actor);
|
|
806
|
+
}, act.context.actor, act.rcOwner);
|
|
738
807
|
};
|
|
739
808
|
task.cancel = () => {
|
|
740
809
|
if (task.done || task.finishing)
|
|
@@ -753,12 +822,44 @@ class ExecutorV1 {
|
|
|
753
822
|
ready();
|
|
754
823
|
return;
|
|
755
824
|
}
|
|
756
|
-
case "task.spawn":
|
|
825
|
+
case "task.spawn":
|
|
826
|
+
case "task.invoke": {
|
|
757
827
|
const group = act.scopes.get(op.scope);
|
|
758
828
|
if (!group)
|
|
759
829
|
throw new Error(`task scope ${op.scope} is missing`);
|
|
760
|
-
const
|
|
761
|
-
|
|
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;
|
|
762
863
|
const task = this.newTask();
|
|
763
864
|
group.tasks.push(task);
|
|
764
865
|
const settle = (value, fault) => {
|
|
@@ -769,9 +870,7 @@ class ExecutorV1 {
|
|
|
769
870
|
!(fault.kind === "panic" && (fault.cancelled || fault.deadline)) &&
|
|
770
871
|
act.context.actor)
|
|
771
872
|
act.context.actor.crashing ??= fault;
|
|
772
|
-
|
|
773
|
-
? this.faultText(act.context.actor.crashing)
|
|
774
|
-
: null, (closeFault) => {
|
|
873
|
+
const complete = (closeFault) => {
|
|
775
874
|
if (!fault || (fault.kind === "panic" && fault.cancelled))
|
|
776
875
|
fault = closeFault ?? fault;
|
|
777
876
|
this.settleTask(task, value, fault);
|
|
@@ -786,32 +885,92 @@ class ExecutorV1 {
|
|
|
786
885
|
}
|
|
787
886
|
for (const changed of [...group.changed])
|
|
788
887
|
changed();
|
|
789
|
-
}
|
|
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
|
+
}
|
|
790
926
|
};
|
|
791
927
|
const context = {
|
|
792
928
|
id: task.id,
|
|
793
929
|
actor: act.context.actor,
|
|
794
|
-
|
|
795
|
-
|
|
930
|
+
rcOwner: act.rcOwner,
|
|
931
|
+
requestDeadline: this.requestDeadline(act),
|
|
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
|
+
},
|
|
796
959
|
};
|
|
797
|
-
const frame = this.activate(this.functionAt(calleeFunction(callable)), callable.kind === "closure" ? [callable.environment] :
|
|
798
|
-
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);
|
|
799
961
|
task.cancel = () => {
|
|
800
962
|
if (task.done || task.finishing)
|
|
801
963
|
return;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
cancelled: true,
|
|
809
|
-
});
|
|
964
|
+
context.pendingCancellation ??= {
|
|
965
|
+
kind: "panic",
|
|
966
|
+
message: "task cancelled",
|
|
967
|
+
cancelled: true,
|
|
968
|
+
};
|
|
969
|
+
context.cancel?.(context.pendingCancellation);
|
|
810
970
|
};
|
|
811
971
|
this.scheduler.enqueue(task.id, () => {
|
|
812
972
|
if (task.done || task.finishing)
|
|
813
973
|
return;
|
|
814
|
-
started = true;
|
|
815
974
|
this.runFrame(frame);
|
|
816
975
|
});
|
|
817
976
|
this.trace.allocateId("task", task.id, act.context.id);
|
|
@@ -888,14 +1047,7 @@ class ExecutorV1 {
|
|
|
888
1047
|
if (receiver.kind !== "dyn") {
|
|
889
1048
|
throw new TypeError(`dyn.call expected a trait object, got ${receiver.kind}`);
|
|
890
1049
|
}
|
|
891
|
-
const
|
|
892
|
-
this.pkg.vtables.find((candidate) => candidate.id === receiver.vtable);
|
|
893
|
-
// The slot number is the checker's own index, so it is matched rather
|
|
894
|
-
// than used as a position in the slot array.
|
|
895
|
-
const entry = vtable?.slots.find((candidate) => candidate.slot === term.slot);
|
|
896
|
-
if (!entry) {
|
|
897
|
-
throw new Error(`vtable ${receiver.vtable} has no slot ${term.slot}`);
|
|
898
|
-
}
|
|
1050
|
+
const entry = this.dynamicEntry(receiver, term.slot);
|
|
899
1051
|
// The wrapped value is the method's receiver, ahead of the call's own
|
|
900
1052
|
// arguments, passed under the decision the slot records: the erasure
|
|
901
1053
|
// the concrete type was made under, not something read off the method.
|
|
@@ -920,7 +1072,7 @@ class ExecutorV1 {
|
|
|
920
1072
|
// `self`. A plain function value carries none.
|
|
921
1073
|
const callee = this.boundary(act, term.callee);
|
|
922
1074
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
923
|
-
this.current = this.activate(this.functionAt(calleeFunction(callee)), callee.kind === "closure" ? [callee.environment, ...args] : args, act, term.result, term.normal, term.unwind);
|
|
1075
|
+
this.current = this.activate(this.functionAt(calleeFunction(callee, term.specialization)), callee.kind === "closure" ? [callee.environment, ...args] : args, act, term.result, term.normal, term.unwind);
|
|
924
1076
|
return;
|
|
925
1077
|
}
|
|
926
1078
|
case "actor.call":
|
|
@@ -928,6 +1080,38 @@ class ExecutorV1 {
|
|
|
928
1080
|
return;
|
|
929
1081
|
case "runtime.call": {
|
|
930
1082
|
const entry = this.pkg.runtime_families[term.family];
|
|
1083
|
+
if (entry?.family === "ActorCaller") {
|
|
1084
|
+
const caller = act.context.caller ?? { kind: "Task" };
|
|
1085
|
+
const payload = [];
|
|
1086
|
+
if (caller.kind === "Actor") {
|
|
1087
|
+
const shape = this.pkg.variants[term.result_shape];
|
|
1088
|
+
const tag = runtimeTag(shape, "CallerActor");
|
|
1089
|
+
payload.push({
|
|
1090
|
+
kind: "record",
|
|
1091
|
+
typeId: shape.cases[tag].fields[0],
|
|
1092
|
+
fields: [{ kind: "i64", value: caller.identity }],
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
this.completeShim(act, term, this.variant(term.result_shape, caller.kind === "Actor"
|
|
1096
|
+
? "CallerActor"
|
|
1097
|
+
: caller.kind === "Timer"
|
|
1098
|
+
? "CallerTimer"
|
|
1099
|
+
: "CallerTask", payload));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (entry?.family === "ActorRefusalLog") {
|
|
1103
|
+
const [message, handler, reason] = term.args.map((argument) => this.boundary(act, argument));
|
|
1104
|
+
this.trace.snapshot("actor.delivery_refused", {
|
|
1105
|
+
actor_id: act.context.actor?.id ?? null,
|
|
1106
|
+
handler: toJsonValue(handler),
|
|
1107
|
+
message: toJsonValue(message),
|
|
1108
|
+
reason: toJsonValue(reason),
|
|
1109
|
+
});
|
|
1110
|
+
if (!act.context.reply)
|
|
1111
|
+
this.trace.writeStderr(`WARN actor.delivery_refused actor=${act.context.actor?.identity ?? 0n} handler=${asString(handler)} reason=${JSON.stringify(asString(reason))}\n`, null);
|
|
1112
|
+
this.completeShim(act, term, UNIT);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
931
1115
|
if (entry &&
|
|
932
1116
|
(entry.family === "Map" ||
|
|
933
1117
|
entry.family === "Set" ||
|
|
@@ -943,6 +1127,9 @@ class ExecutorV1 {
|
|
|
943
1127
|
[
|
|
944
1128
|
"RcNew",
|
|
945
1129
|
"RcClone",
|
|
1130
|
+
"RcDowngrade",
|
|
1131
|
+
"WeakCloneRc",
|
|
1132
|
+
"WeakUpgradeRc",
|
|
946
1133
|
"RcGet",
|
|
947
1134
|
"RcIsUnique",
|
|
948
1135
|
"RcSet",
|
|
@@ -950,7 +1137,7 @@ class ExecutorV1 {
|
|
|
950
1137
|
"RcWeakCount",
|
|
951
1138
|
].includes(entry.family)) {
|
|
952
1139
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
953
|
-
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] : [])));
|
|
954
1141
|
return;
|
|
955
1142
|
}
|
|
956
1143
|
if (entry?.family === "SupervisorPool") {
|
|
@@ -966,13 +1153,40 @@ class ExecutorV1 {
|
|
|
966
1153
|
else
|
|
967
1154
|
this.completeShim(act, term, UNIT);
|
|
968
1155
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
969
|
-
});
|
|
1156
|
+
}, act.context.actor, act.rcOwner);
|
|
970
1157
|
return;
|
|
971
1158
|
}
|
|
972
1159
|
if (entry?.family === "StructuralFormat") {
|
|
973
1160
|
this.structuralFormat(act, term);
|
|
974
1161
|
return;
|
|
975
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
|
+
}
|
|
976
1190
|
const shim = entry ? resolveRuntimeShim(entry) : undefined;
|
|
977
1191
|
if (!entry || !shim) {
|
|
978
1192
|
throw new Error(`runtime family ${term.family} resolved no shim after admission`);
|
|
@@ -1031,6 +1245,7 @@ class ExecutorV1 {
|
|
|
1031
1245
|
this.takeEdge(act, act.fault ? term.fault : term.normal);
|
|
1032
1246
|
return;
|
|
1033
1247
|
case "enter_defer":
|
|
1248
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) + 1;
|
|
1034
1249
|
// The deferred body runs on its own fault state: a body reached while
|
|
1035
1250
|
// unwinding must not read the fault it is cleaning up after.
|
|
1036
1251
|
act.parked.push(act.fault);
|
|
@@ -1038,6 +1253,7 @@ class ExecutorV1 {
|
|
|
1038
1253
|
this.takeEdge(act, term.body);
|
|
1039
1254
|
return;
|
|
1040
1255
|
case "finish_defer": {
|
|
1256
|
+
act.context.pollSuppression = (act.context.pollSuppression ?? 0) - 1;
|
|
1041
1257
|
// Combine the parked fault with whatever the body left, keeping the
|
|
1042
1258
|
// first: a join must never forget the fault the boundary carried in.
|
|
1043
1259
|
const parked = act.parked.pop();
|
|
@@ -1067,12 +1283,7 @@ class ExecutorV1 {
|
|
|
1067
1283
|
typeId: shape.name,
|
|
1068
1284
|
shape: shape.id,
|
|
1069
1285
|
tag: deadline ? term.deadline_variant : term.fault_variant,
|
|
1070
|
-
payload: [
|
|
1071
|
-
{
|
|
1072
|
-
kind: "string",
|
|
1073
|
-
value: faultReport(fault),
|
|
1074
|
-
},
|
|
1075
|
-
],
|
|
1286
|
+
payload: [stringValue(faultReport(fault))],
|
|
1076
1287
|
};
|
|
1077
1288
|
act.fault = null;
|
|
1078
1289
|
if (act.context.actor)
|
|
@@ -1167,7 +1378,7 @@ class ExecutorV1 {
|
|
|
1167
1378
|
this.keepInputs(act, term, args);
|
|
1168
1379
|
this.raiseFault(act, fault, term.unwind);
|
|
1169
1380
|
resume();
|
|
1170
|
-
});
|
|
1381
|
+
}, act.context.actor, act.rcOwner);
|
|
1171
1382
|
};
|
|
1172
1383
|
const next = (value = UNIT) => {
|
|
1173
1384
|
let step;
|
|
@@ -1185,6 +1396,9 @@ class ExecutorV1 {
|
|
|
1185
1396
|
this.completeShim(act, term, step.value);
|
|
1186
1397
|
resume();
|
|
1187
1398
|
}
|
|
1399
|
+
else if (step.value.kind === "fault") {
|
|
1400
|
+
fail({ kind: "panic", message: step.value.message });
|
|
1401
|
+
}
|
|
1188
1402
|
else if (step.value.kind === "release") {
|
|
1189
1403
|
const request = step.value;
|
|
1190
1404
|
this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
|
|
@@ -1198,11 +1412,11 @@ class ExecutorV1 {
|
|
|
1198
1412
|
act.fault ??= fault;
|
|
1199
1413
|
}
|
|
1200
1414
|
next();
|
|
1201
|
-
});
|
|
1415
|
+
}, act.context.actor, act.rcOwner);
|
|
1202
1416
|
}
|
|
1203
1417
|
else {
|
|
1204
1418
|
const request = step.value;
|
|
1205
|
-
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);
|
|
1206
1420
|
act.context.cancel = (fault) => child.cancel?.(fault);
|
|
1207
1421
|
}
|
|
1208
1422
|
};
|
|
@@ -1211,8 +1425,17 @@ class ExecutorV1 {
|
|
|
1211
1425
|
}
|
|
1212
1426
|
supervisorPool(act, term, operation) {
|
|
1213
1427
|
const [view, index] = term.args.map((arg) => this.boundary(act, arg));
|
|
1214
|
-
if (view?.kind !== "vector"
|
|
1215
|
-
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");
|
|
1216
1439
|
const value = view.items[Number(index.value)];
|
|
1217
1440
|
if (operation === "Get") {
|
|
1218
1441
|
this.completeShim(act, term, this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : []));
|
|
@@ -1232,7 +1455,7 @@ class ExecutorV1 {
|
|
|
1232
1455
|
const continueRendering = () => {
|
|
1233
1456
|
const next = renderer.next();
|
|
1234
1457
|
if (typeof next === "string") {
|
|
1235
|
-
this.completeShim(act, term,
|
|
1458
|
+
this.completeShim(act, term, stringValue(next));
|
|
1236
1459
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1237
1460
|
return;
|
|
1238
1461
|
}
|
|
@@ -1257,7 +1480,92 @@ class ExecutorV1 {
|
|
|
1257
1480
|
}
|
|
1258
1481
|
this.takeEdge(act, term.normal);
|
|
1259
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
|
+
}
|
|
1260
1566
|
returnFrom(act, value) {
|
|
1567
|
+
if (this.finishConstruction(act, () => this.returnFrom(act, value)))
|
|
1568
|
+
return;
|
|
1261
1569
|
const caller = act.caller;
|
|
1262
1570
|
if (!caller) {
|
|
1263
1571
|
this.running = false;
|
|
@@ -1282,6 +1590,14 @@ class ExecutorV1 {
|
|
|
1282
1590
|
this.current = caller;
|
|
1283
1591
|
this.takeEdge(caller, act.normal);
|
|
1284
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
|
+
}
|
|
1285
1601
|
/// The wrapped value on its way to a trait method's `self`. A `copy` receiver
|
|
1286
1602
|
/// deep-clones, a `move` invalidates the trait object it came out of, and a
|
|
1287
1603
|
/// borrow passes the live value.
|
|
@@ -1309,6 +1625,8 @@ class ExecutorV1 {
|
|
|
1309
1625
|
this.takeEdge(act, unwind);
|
|
1310
1626
|
}
|
|
1311
1627
|
resumeUnwind(act, handback) {
|
|
1628
|
+
if (this.finishConstruction(act, () => this.resumeUnwind(act, handback)))
|
|
1629
|
+
return;
|
|
1312
1630
|
const fault = act.fault;
|
|
1313
1631
|
if (!fault) {
|
|
1314
1632
|
throw new Error(`${act.fn.name}: resume_unwind with no pending fault`);
|
|
@@ -1316,7 +1634,7 @@ class ExecutorV1 {
|
|
|
1316
1634
|
const caller = act.caller;
|
|
1317
1635
|
if (!caller) {
|
|
1318
1636
|
this.running = false;
|
|
1319
|
-
act.context.failed(fault);
|
|
1637
|
+
act.context.failed(fault, handback);
|
|
1320
1638
|
return;
|
|
1321
1639
|
}
|
|
1322
1640
|
if (!act.unwind) {
|
|
@@ -1386,37 +1704,27 @@ class ExecutorV1 {
|
|
|
1386
1704
|
return;
|
|
1387
1705
|
}
|
|
1388
1706
|
case "StreamSend": {
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
let closing = false;
|
|
1393
|
-
let cancelled = null;
|
|
1394
|
-
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);
|
|
1395
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);
|
|
1396
1714
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1397
|
-
|
|
1398
|
-
cancelWait();
|
|
1399
|
-
if (owned) {
|
|
1400
|
-
owned = false;
|
|
1401
|
-
closing = true;
|
|
1402
|
-
this.closeValueAsync(value, this.faultText(fault), (closeFault) => cancel(closeFault ?? fault));
|
|
1403
|
-
}
|
|
1404
|
-
else if (!closing)
|
|
1405
|
-
cancel(fault);
|
|
1715
|
+
stop(fault, (closeFault) => cancel?.(closeFault ?? fault));
|
|
1406
1716
|
};
|
|
1407
|
-
cancelWait = this.pipes.send(sink, value, term.detail.park, (status) => {
|
|
1408
|
-
owned = false;
|
|
1409
|
-
if (status === 0)
|
|
1410
|
-
wake(UNIT);
|
|
1411
|
-
else {
|
|
1412
|
-
closing = true;
|
|
1413
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => wake(UNIT, fault ?? cancelled, status));
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
1717
|
return;
|
|
1417
1718
|
}
|
|
1418
1719
|
case "StreamNext": {
|
|
1419
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
|
+
}
|
|
1420
1728
|
let cancelWait = () => { };
|
|
1421
1729
|
const wake = this.park(act, term, () => cancelWait());
|
|
1422
1730
|
cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault) => {
|
|
@@ -1429,10 +1737,13 @@ class ExecutorV1 {
|
|
|
1429
1737
|
}
|
|
1430
1738
|
case "Await": {
|
|
1431
1739
|
const task = this.taskFor(this.boundary(act, term.inputs[0]));
|
|
1432
|
-
|
|
1740
|
+
let unwatch = () => { };
|
|
1741
|
+
const wake = this.park(act, term, () => unwatch());
|
|
1433
1742
|
const ready = () => {
|
|
1434
|
-
if (!task.done)
|
|
1743
|
+
if (!task.done) {
|
|
1744
|
+
this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
|
|
1435
1745
|
return;
|
|
1746
|
+
}
|
|
1436
1747
|
if (task.fault)
|
|
1437
1748
|
wake(UNIT, task.fault);
|
|
1438
1749
|
else {
|
|
@@ -1442,6 +1753,11 @@ class ExecutorV1 {
|
|
|
1442
1753
|
}
|
|
1443
1754
|
};
|
|
1444
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
|
+
};
|
|
1445
1761
|
ready();
|
|
1446
1762
|
return;
|
|
1447
1763
|
}
|
|
@@ -1504,6 +1820,11 @@ class ExecutorV1 {
|
|
|
1504
1820
|
cancelTimer?.();
|
|
1505
1821
|
wake({ kind: "i64", value: BigInt(ready[0].index) });
|
|
1506
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
|
+
}
|
|
1507
1828
|
};
|
|
1508
1829
|
if (hasTimeout) {
|
|
1509
1830
|
const duration = values[values.length - 1];
|
|
@@ -1531,18 +1852,39 @@ class ExecutorV1 {
|
|
|
1531
1852
|
const protocol = term.detail;
|
|
1532
1853
|
const inputs = term.inputs.map((input) => this.boundary(act, input));
|
|
1533
1854
|
const wake = this.park(act, term);
|
|
1855
|
+
let target;
|
|
1856
|
+
let checkWait;
|
|
1534
1857
|
const cancelRequest = this.ask(protocol, inputs, (value, error, closeFault) => {
|
|
1535
1858
|
act.fault ??= closeFault ?? null;
|
|
1536
1859
|
return wake(this.completion(term.result_shape, term.error_shape, value, error));
|
|
1537
|
-
}, term.request_shapes)
|
|
1860
|
+
}, term.request_shapes, (actor) => {
|
|
1861
|
+
target = actor;
|
|
1862
|
+
checkWait?.();
|
|
1863
|
+
});
|
|
1538
1864
|
const cancel = act.context.cancel;
|
|
1539
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();
|
|
1540
1868
|
return;
|
|
1541
1869
|
}
|
|
1542
1870
|
case "NativeIo": {
|
|
1543
1871
|
if (term.detail?.operation === "SinkFinish") {
|
|
1544
|
-
this.
|
|
1545
|
-
|
|
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
|
+
});
|
|
1546
1888
|
return;
|
|
1547
1889
|
}
|
|
1548
1890
|
// Admission passed `StdinReadLine`. The record is the raw line,
|
|
@@ -1552,6 +1894,40 @@ class ExecutorV1 {
|
|
|
1552
1894
|
this.park(act, term)(bytesValue([...line]));
|
|
1553
1895
|
return;
|
|
1554
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
|
+
}
|
|
1555
1931
|
case "Sleep": {
|
|
1556
1932
|
const duration = term.inputs[0]
|
|
1557
1933
|
? this.boundary(act, term.inputs[0])
|
|
@@ -1566,13 +1942,34 @@ class ExecutorV1 {
|
|
|
1566
1942
|
throw new Error(`suspension kind ${term.kind} resolved no handler after admission`);
|
|
1567
1943
|
}
|
|
1568
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
|
+
}
|
|
1569
1962
|
park(act, term, cleanup = () => { }) {
|
|
1570
1963
|
this.running = false;
|
|
1964
|
+
act.context.parked = true;
|
|
1571
1965
|
let pending = null;
|
|
1572
1966
|
const resume = (value, fault = null, edge = 0) => {
|
|
1573
1967
|
if (pending)
|
|
1574
1968
|
return false;
|
|
1575
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);
|
|
1576
1973
|
cleanup();
|
|
1577
1974
|
this.scheduler.enqueue(act.context.id, () => {
|
|
1578
1975
|
const outcome = pending;
|
|
@@ -1581,7 +1978,8 @@ class ExecutorV1 {
|
|
|
1581
1978
|
const fault = closeFault ?? (outcome.fault ? (act.fault ?? outcome.fault) : null);
|
|
1582
1979
|
if (fault) {
|
|
1583
1980
|
act.fault = fault;
|
|
1584
|
-
this.takeEdge(act,
|
|
1981
|
+
this.takeEdge(act, term.kind === "Safepoint" ||
|
|
1982
|
+
(fault.kind === "panic" && fault.cancelled)
|
|
1585
1983
|
? term.cancel
|
|
1586
1984
|
: term.unwind);
|
|
1587
1985
|
}
|
|
@@ -1599,13 +1997,15 @@ class ExecutorV1 {
|
|
|
1599
1997
|
// the cancellation latch live through that window and retire a result
|
|
1600
1998
|
// whose normal edge the consumer never entered.
|
|
1601
1999
|
if (outcome.fault)
|
|
1602
|
-
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);
|
|
1603
2001
|
else
|
|
1604
2002
|
finish();
|
|
1605
2003
|
});
|
|
1606
2004
|
return true;
|
|
1607
2005
|
};
|
|
1608
2006
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
2007
|
+
if (term.cancel == null)
|
|
2008
|
+
return;
|
|
1609
2009
|
if (pending)
|
|
1610
2010
|
pending.fault ??= fault;
|
|
1611
2011
|
else
|
|
@@ -1613,6 +2013,222 @@ class ExecutorV1 {
|
|
|
1613
2013
|
};
|
|
1614
2014
|
return resume;
|
|
1615
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
|
+
}
|
|
1616
2232
|
newPipe(capacity) {
|
|
1617
2233
|
const id = this.ids.channel();
|
|
1618
2234
|
this.trace.allocateId("channel", id, this.current?.context.id ?? "actor:root");
|
|
@@ -1638,7 +2254,7 @@ class ExecutorV1 {
|
|
|
1638
2254
|
act.fault = combineFaults(act.fault, fault);
|
|
1639
2255
|
if (!synchronous)
|
|
1640
2256
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
1641
|
-
});
|
|
2257
|
+
}, act.context.actor, act.rcOwner);
|
|
1642
2258
|
synchronous = false;
|
|
1643
2259
|
if (!completed)
|
|
1644
2260
|
this.running = false;
|
|
@@ -1663,7 +2279,7 @@ class ExecutorV1 {
|
|
|
1663
2279
|
receive?.(null, generator.fault);
|
|
1664
2280
|
for (const closed of generator.closed.splice(0))
|
|
1665
2281
|
closed(generator.fault);
|
|
1666
|
-
});
|
|
2282
|
+
}, generator.frame.context.actor, generator.frame.rcOwner);
|
|
1667
2283
|
}
|
|
1668
2284
|
closeGenerator(generator, done) {
|
|
1669
2285
|
if (generator.done) {
|
|
@@ -1676,7 +2292,7 @@ class ExecutorV1 {
|
|
|
1676
2292
|
else
|
|
1677
2293
|
generator.frame.context.cancel?.();
|
|
1678
2294
|
}
|
|
1679
|
-
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) {
|
|
1680
2296
|
const pending = [value];
|
|
1681
2297
|
let firstFault = null;
|
|
1682
2298
|
const next = (failure = null) => {
|
|
@@ -1687,15 +2303,35 @@ class ExecutorV1 {
|
|
|
1687
2303
|
while (pending.length) {
|
|
1688
2304
|
const value = pending.pop();
|
|
1689
2305
|
switch (value.kind) {
|
|
2306
|
+
case "string":
|
|
2307
|
+
value.release();
|
|
2308
|
+
break;
|
|
1690
2309
|
case "rc":
|
|
1691
2310
|
if (!value.closed) {
|
|
1692
2311
|
value.closed = true;
|
|
1693
|
-
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;
|
|
1694
2315
|
pending.push(value.cell.value);
|
|
1695
2316
|
value.cell.value = UNIT;
|
|
1696
2317
|
}
|
|
1697
2318
|
}
|
|
1698
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
|
+
}
|
|
1699
2335
|
case "task": {
|
|
1700
2336
|
// Dropping a handle does not cancel scoped work. Join drains the
|
|
1701
2337
|
// task, and scope close releases any result that was never taken.
|
|
@@ -1703,7 +2339,7 @@ class ExecutorV1 {
|
|
|
1703
2339
|
if (task?.done) {
|
|
1704
2340
|
const result = task.value;
|
|
1705
2341
|
task.value = UNIT;
|
|
1706
|
-
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);
|
|
1707
2343
|
return;
|
|
1708
2344
|
}
|
|
1709
2345
|
if (task?.close) {
|
|
@@ -1731,7 +2367,7 @@ class ExecutorV1 {
|
|
|
1731
2367
|
if (this.consumedResources.has(value))
|
|
1732
2368
|
break;
|
|
1733
2369
|
if (!this.closingResources.has(value)) {
|
|
1734
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next);
|
|
2370
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
1735
2371
|
return;
|
|
1736
2372
|
}
|
|
1737
2373
|
this.consumedResources.add(value);
|
|
@@ -1745,10 +2381,36 @@ class ExecutorV1 {
|
|
|
1745
2381
|
case "vector":
|
|
1746
2382
|
pending.push(...[...value.items].reverse());
|
|
1747
2383
|
break;
|
|
1748
|
-
case "
|
|
1749
|
-
|
|
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
|
+
}
|
|
1750
2396
|
pending.push(...this.pipes.close(value, currentFault).reverse());
|
|
1751
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
|
+
}
|
|
1752
2414
|
}
|
|
1753
2415
|
}
|
|
1754
2416
|
done(firstFault);
|
|
@@ -1756,6 +2418,8 @@ class ExecutorV1 {
|
|
|
1756
2418
|
next();
|
|
1757
2419
|
}
|
|
1758
2420
|
closeValue(value, fault) {
|
|
2421
|
+
if (value.kind === "string")
|
|
2422
|
+
value.release();
|
|
1759
2423
|
if (value.kind === "closure")
|
|
1760
2424
|
this.closeValue(value.environment, fault);
|
|
1761
2425
|
else if (value.kind === "sink" || value.kind === "stream")
|
|
@@ -1804,25 +2468,33 @@ class ExecutorV1 {
|
|
|
1804
2468
|
for (const changed of [...task.changed])
|
|
1805
2469
|
changed();
|
|
1806
2470
|
}
|
|
1807
|
-
invokeFrame(actor, callable, args, returned, failed) {
|
|
1808
|
-
const
|
|
2471
|
+
invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false) {
|
|
2472
|
+
const context = {
|
|
1809
2473
|
id: actor?.id ?? "actor:root",
|
|
1810
2474
|
actor,
|
|
2475
|
+
rcOwner,
|
|
1811
2476
|
returned,
|
|
1812
|
-
failed,
|
|
1813
|
-
}
|
|
2477
|
+
failed: (fault) => failed(fault),
|
|
2478
|
+
};
|
|
2479
|
+
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
1814
2480
|
let cancelled = null;
|
|
1815
|
-
|
|
2481
|
+
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
1816
2482
|
cancelled = fault;
|
|
1817
2483
|
};
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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;
|
|
1826
2498
|
}
|
|
1827
2499
|
variant(shapeId, role, payload = []) {
|
|
1828
2500
|
const shape = shapeId == null ? undefined : this.pkg.variants[shapeId];
|
|
@@ -1866,13 +2538,26 @@ class ExecutorV1 {
|
|
|
1866
2538
|
actorCall(act, term) {
|
|
1867
2539
|
const args = term.args.map((arg) => this.boundary(act, arg));
|
|
1868
2540
|
const operation = term.operation;
|
|
2541
|
+
const caller = act.context.actor
|
|
2542
|
+
? { kind: "Actor", identity: act.context.actor.identity }
|
|
2543
|
+
: { kind: "Task" };
|
|
1869
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
|
+
}
|
|
1870
2553
|
case "spawn": {
|
|
1871
2554
|
const layout = this.pkg.actors?.find((actor) => actor.id === operation.actor);
|
|
1872
2555
|
if (!layout)
|
|
1873
2556
|
throw new Error(`actor layout ${operation.actor} is missing`);
|
|
1874
2557
|
let supplied = 0;
|
|
1875
2558
|
const actor = {
|
|
2559
|
+
rcOwner: act.rcOwner ?? { blocks: new Set(), closed: false },
|
|
2560
|
+
identity: ++this.actorIdentity,
|
|
1876
2561
|
id: this.ids.actor(),
|
|
1877
2562
|
layout,
|
|
1878
2563
|
state: {
|
|
@@ -1881,9 +2566,11 @@ class ExecutorV1 {
|
|
|
1881
2566
|
fields: layout.state_fields.map((field) => field.deferred ? UNIT : (args[supplied++] ?? UNIT)),
|
|
1882
2567
|
},
|
|
1883
2568
|
mailbox: [],
|
|
2569
|
+
timers: new Set(),
|
|
1884
2570
|
disposals: [],
|
|
1885
2571
|
admission: [],
|
|
1886
2572
|
busy: true,
|
|
2573
|
+
constructing: true,
|
|
1887
2574
|
alive: true,
|
|
1888
2575
|
completed: false,
|
|
1889
2576
|
closing: false,
|
|
@@ -1893,44 +2580,181 @@ class ExecutorV1 {
|
|
|
1893
2580
|
closingFault: null,
|
|
1894
2581
|
crashing: null,
|
|
1895
2582
|
active: null,
|
|
1896
|
-
|
|
2583
|
+
terminalHook: null,
|
|
1897
2584
|
closed: [],
|
|
1898
2585
|
};
|
|
1899
2586
|
this.actors.set(actor.id, actor);
|
|
1900
2587
|
this.trace.allocateId("actor", actor.id, act.context.id);
|
|
1901
2588
|
this.trace.snapshot("actor.spawn", { actor_id: actor.id });
|
|
1902
2589
|
const handle = { kind: "actor", id: actor.id };
|
|
1903
|
-
this.running = false;
|
|
1904
2590
|
const failed = (fault) => {
|
|
2591
|
+
actor.active = null;
|
|
1905
2592
|
actor.alive = false;
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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);
|
|
1914
2608
|
});
|
|
1915
2609
|
};
|
|
1916
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;
|
|
1917
2622
|
actor.busy = false;
|
|
2623
|
+
this.startPeriodicTimers(actor);
|
|
1918
2624
|
this.dispatchActor(actor);
|
|
1919
2625
|
this.completeShim(act, term, handle);
|
|
1920
|
-
this.
|
|
2626
|
+
this.current = act;
|
|
2627
|
+
this.running = true;
|
|
1921
2628
|
};
|
|
2629
|
+
// Construction enters the same interpreter loop as its caller. Only
|
|
2630
|
+
// a real suspension queues work and advances deterministic scheduling.
|
|
1922
2631
|
const start = () => {
|
|
1923
2632
|
if (layout.start !== undefined)
|
|
1924
|
-
this.invokeFrame(actor, layout.start, [actor.state], started, failed);
|
|
2633
|
+
actor.active = this.invokeFrame(actor, layout.start, [actor.state], started, failed, actor.rcOwner, true);
|
|
1925
2634
|
else
|
|
1926
2635
|
started();
|
|
1927
2636
|
};
|
|
1928
2637
|
if (layout.init !== undefined)
|
|
1929
|
-
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);
|
|
1930
2639
|
else
|
|
1931
2640
|
start();
|
|
1932
2641
|
return;
|
|
1933
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
|
+
}
|
|
1934
2758
|
case "stream_start": {
|
|
1935
2759
|
const [target, payload] = args;
|
|
1936
2760
|
const fields = fieldsOf(payload);
|
|
@@ -1956,6 +2780,7 @@ class ExecutorV1 {
|
|
|
1956
2780
|
if (!handler)
|
|
1957
2781
|
throw new Error("stream request has no producer handler");
|
|
1958
2782
|
actor.mailbox.push({
|
|
2783
|
+
caller,
|
|
1959
2784
|
handler,
|
|
1960
2785
|
payload: fields,
|
|
1961
2786
|
reply: false,
|
|
@@ -1980,6 +2805,10 @@ class ExecutorV1 {
|
|
|
1980
2805
|
task.actorError = error;
|
|
1981
2806
|
this.settleTask(task, value ?? UNIT, closeFault ?? null);
|
|
1982
2807
|
return true;
|
|
2808
|
+
}, undefined, (actor) => {
|
|
2809
|
+
task.actorTarget = actor;
|
|
2810
|
+
for (const changed of [...task.changed])
|
|
2811
|
+
changed();
|
|
1983
2812
|
});
|
|
1984
2813
|
task.close = (done) => cancelRequest((fault) => {
|
|
1985
2814
|
this.settleTask(task, UNIT, fault);
|
|
@@ -2016,15 +2845,17 @@ class ExecutorV1 {
|
|
|
2016
2845
|
this.variant(shapes.success, discarded ? "DeliveryDiscarded" : "DeliveryAccepted"),
|
|
2017
2846
|
]);
|
|
2018
2847
|
const rejected = (reason) => this.variant(term.result_shape, "ResultErr", [
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
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
|
+
]),
|
|
2028
2859
|
]);
|
|
2029
2860
|
let parked = false;
|
|
2030
2861
|
const finish = (value) => {
|
|
@@ -2037,85 +2868,38 @@ class ExecutorV1 {
|
|
|
2037
2868
|
finish(rejected("Dead"));
|
|
2038
2869
|
return;
|
|
2039
2870
|
}
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
finish(report);
|
|
2048
|
-
};
|
|
2049
|
-
const discard = (value, report) => {
|
|
2050
|
-
let settled = false;
|
|
2051
|
-
let waiting = false;
|
|
2052
|
-
this.closeValueAsync(value, this.pipeFault(act), (fault) => {
|
|
2053
|
-
act.fault ??= fault;
|
|
2054
|
-
settled = true;
|
|
2055
|
-
if (waiting)
|
|
2056
|
-
finish(report);
|
|
2057
|
-
else
|
|
2058
|
-
this.completeShim(act, term, report);
|
|
2059
|
-
}, act.context.actor);
|
|
2060
|
-
if (!settled) {
|
|
2061
|
-
waiting = true;
|
|
2062
|
-
parked = true;
|
|
2063
|
-
this.running = false;
|
|
2064
|
-
}
|
|
2871
|
+
const message = {
|
|
2872
|
+
caller,
|
|
2873
|
+
handler,
|
|
2874
|
+
payload: payload.fields,
|
|
2875
|
+
reply: false,
|
|
2876
|
+
obligated: false,
|
|
2877
|
+
complete: (_value, _error, drained) => drained?.(null),
|
|
2065
2878
|
};
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
if (declared === "coalesce" && coalesce) {
|
|
2069
|
-
const key = coalesce.keys.find((key) => key.message === handler.message_id);
|
|
2070
|
-
const queued = key
|
|
2071
|
-
? actor.mailbox.findIndex((message) => !message.obligated &&
|
|
2072
|
-
message.handler.message_id === handler.message_id &&
|
|
2073
|
-
equals(message.payload[key.param], payload.fields[key.param]))
|
|
2074
|
-
: -1;
|
|
2075
|
-
if (queued >= 0) {
|
|
2076
|
-
const superseded = actor.mailbox[queued];
|
|
2077
|
-
actor.mailbox[queued] = {
|
|
2078
|
-
...superseded,
|
|
2079
|
-
payload: payload.fields,
|
|
2080
|
-
};
|
|
2081
|
-
discardAtReceiver({ kind: "record", typeId: "", fields: superseded.payload }, accepted(false));
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
declared = coalesce.fallback;
|
|
2085
|
-
}
|
|
2086
|
-
if (declared === "drop_new") {
|
|
2087
|
-
discardAtReceiver(payload, accepted(true));
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
// A queued completion call owes its caller a reply, so `drop_old`
|
|
2091
|
-
// evicts only a one-way message and refuses when there is none.
|
|
2092
|
-
const oldest = declared === "drop_old"
|
|
2093
|
-
? actor.mailbox.findIndex((message) => !message.obligated)
|
|
2094
|
-
: -1;
|
|
2095
|
-
if (oldest >= 0) {
|
|
2096
|
-
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
2097
|
-
actor.mailbox.push({
|
|
2098
|
-
handler,
|
|
2099
|
-
payload: payload.fields,
|
|
2100
|
-
reply: false,
|
|
2101
|
-
obligated: false,
|
|
2102
|
-
complete: (_value, _error, drained) => drained?.(null),
|
|
2103
|
-
});
|
|
2104
|
-
this.dispatchActor(actor);
|
|
2105
|
-
discardAtReceiver({ kind: "record", typeId: "", fields: evicted.payload }, accepted(false));
|
|
2106
|
-
return;
|
|
2107
|
-
}
|
|
2108
|
-
if (full) {
|
|
2879
|
+
const status = this.admitActorMessage(actor, message);
|
|
2880
|
+
if (status === "full") {
|
|
2109
2881
|
if (operation.policy === "drop_newest") {
|
|
2110
|
-
|
|
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
|
+
}
|
|
2111
2897
|
return;
|
|
2112
2898
|
}
|
|
2113
2899
|
if (operation.policy === "reject") {
|
|
2114
2900
|
finish(rejected("Full"));
|
|
2115
2901
|
return;
|
|
2116
2902
|
}
|
|
2117
|
-
// The checker already resolved declaration defaults and explicit
|
|
2118
|
-
// overrides; the declaration's own resolution happened above.
|
|
2119
2903
|
if (operation.policy !== "wait")
|
|
2120
2904
|
throw new Error("submission has no admitted policy");
|
|
2121
2905
|
parked = true;
|
|
@@ -2123,19 +2907,10 @@ class ExecutorV1 {
|
|
|
2123
2907
|
actor.admission.push(submit);
|
|
2124
2908
|
return;
|
|
2125
2909
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
obligated: false,
|
|
2131
|
-
complete: (_value, _error, drained) => drained?.(null),
|
|
2132
|
-
});
|
|
2133
|
-
this.trace.snapshot("actor.send", {
|
|
2134
|
-
actor_id: actor.id,
|
|
2135
|
-
handler: handler.name,
|
|
2136
|
-
});
|
|
2137
|
-
this.dispatchActor(actor);
|
|
2138
|
-
finish(accepted(false));
|
|
2910
|
+
if (status === "closed")
|
|
2911
|
+
finish(rejected("Dead"));
|
|
2912
|
+
else
|
|
2913
|
+
finish(accepted(status === "discarded"));
|
|
2139
2914
|
};
|
|
2140
2915
|
submit();
|
|
2141
2916
|
return;
|
|
@@ -2144,15 +2919,32 @@ class ExecutorV1 {
|
|
|
2144
2919
|
const layout = this.pkg.supervisors?.find((layout) => layout.id === operation.supervisor);
|
|
2145
2920
|
if (!layout)
|
|
2146
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;
|
|
2147
2941
|
const owner = {
|
|
2148
2942
|
id: this.ids.supervisor(),
|
|
2149
2943
|
layout,
|
|
2150
|
-
config
|
|
2151
|
-
children: layout.children.flatMap((
|
|
2152
|
-
specs: layout.children.flatMap((child) => Array.from({ length:
|
|
2153
|
-
offsets: layout.children.map((_child, index) =>
|
|
2154
|
-
.slice(0, index)
|
|
2155
|
-
.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),
|
|
2156
2948
|
recovering: new Set(),
|
|
2157
2949
|
restartBusy: false,
|
|
2158
2950
|
pendingRestarts: [],
|
|
@@ -2211,7 +3003,7 @@ class ExecutorV1 {
|
|
|
2211
3003
|
this.completeShim(act, term, {
|
|
2212
3004
|
kind: "vector",
|
|
2213
3005
|
elementType: "role",
|
|
2214
|
-
items: Array.from({ length:
|
|
3006
|
+
items: Array.from({ length: owner.children.length }, (_, index) => makeRole(offset + index)),
|
|
2215
3007
|
});
|
|
2216
3008
|
return;
|
|
2217
3009
|
}
|
|
@@ -2221,12 +3013,16 @@ class ExecutorV1 {
|
|
|
2221
3013
|
case "await_restarted": {
|
|
2222
3014
|
const target = args[0];
|
|
2223
3015
|
const role = "id" in target ? this.roles.get(target.id) : undefined;
|
|
2224
|
-
if (!role)
|
|
2225
|
-
|
|
2226
|
-
|
|
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)) {
|
|
2227
3023
|
this.running = false;
|
|
2228
3024
|
const settled = () => {
|
|
2229
|
-
if (this.
|
|
3025
|
+
if (this.roleRestartPending(role)) {
|
|
2230
3026
|
role.waiting.push(settled);
|
|
2231
3027
|
return;
|
|
2232
3028
|
}
|
|
@@ -2266,7 +3062,10 @@ class ExecutorV1 {
|
|
|
2266
3062
|
if (target.kind === "actor") {
|
|
2267
3063
|
const actor = this.actorFor(target);
|
|
2268
3064
|
const complete = () => {
|
|
2269
|
-
|
|
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;
|
|
2270
3069
|
if (actor.closeFaultDebt !== undefined)
|
|
2271
3070
|
this.faultDebts.delete(actor.closeFaultDebt);
|
|
2272
3071
|
if (fault)
|
|
@@ -2312,15 +3111,41 @@ class ExecutorV1 {
|
|
|
2312
3111
|
throw new Error(`actor operation ${operation.op} has no executor`);
|
|
2313
3112
|
}
|
|
2314
3113
|
}
|
|
2315
|
-
|
|
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
|
+
}
|
|
3122
|
+
requestDeadline(act) {
|
|
3123
|
+
let deadline = act.context.requestDeadline;
|
|
3124
|
+
for (let frame = act; frame; frame = frame.caller) {
|
|
3125
|
+
for (const group of frame.scopes.values()) {
|
|
3126
|
+
if (group.expiresAt !== undefined)
|
|
3127
|
+
deadline = Math.min(deadline ?? Infinity, group.expiresAt);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return deadline;
|
|
3131
|
+
}
|
|
3132
|
+
ask(protocol, inputs, complete, shapes, selectedTarget) {
|
|
2316
3133
|
let settled = false;
|
|
2317
3134
|
let admitted = false;
|
|
2318
3135
|
let closing = false;
|
|
2319
3136
|
const owner = this.current.context.actor;
|
|
3137
|
+
const rcOwner = this.current.rcOwner;
|
|
3138
|
+
const deadline = this.requestDeadline(this.current);
|
|
3139
|
+
let cancelTimer;
|
|
3140
|
+
let message;
|
|
3141
|
+
const caller = owner
|
|
3142
|
+
? { kind: "Actor", identity: owner.identity }
|
|
3143
|
+
: { kind: "Task" };
|
|
2320
3144
|
let receiver = null;
|
|
2321
3145
|
const finish = (value, error, drained) => {
|
|
2322
3146
|
const alreadySettled = settled;
|
|
2323
3147
|
settled = true;
|
|
3148
|
+
cancelTimer?.();
|
|
2324
3149
|
if (alreadySettled || complete(value, error) === false) {
|
|
2325
3150
|
// The finishing receiver still owns a reply that its caller no
|
|
2326
3151
|
// longer accepts. Its next turn must wait for this release.
|
|
@@ -2331,7 +3156,7 @@ class ExecutorV1 {
|
|
|
2331
3156
|
this.crashActor(owner, fault);
|
|
2332
3157
|
else if (fault)
|
|
2333
3158
|
this.rootFault ??= fault;
|
|
2334
|
-
}, admitted ? receiver : owner);
|
|
3159
|
+
}, admitted ? receiver : owner, admitted ? (receiver?.rcOwner ?? null) : rcOwner);
|
|
2335
3160
|
}
|
|
2336
3161
|
else
|
|
2337
3162
|
drained?.(null);
|
|
@@ -2355,7 +3180,7 @@ class ExecutorV1 {
|
|
|
2355
3180
|
closeFault = fault;
|
|
2356
3181
|
for (const waiter of closeWaiters.splice(0))
|
|
2357
3182
|
waiter(fault);
|
|
2358
|
-
}, owner);
|
|
3183
|
+
}, owner, rcOwner);
|
|
2359
3184
|
};
|
|
2360
3185
|
const reject = (reason) => {
|
|
2361
3186
|
if (settled || closing)
|
|
@@ -2364,12 +3189,17 @@ class ExecutorV1 {
|
|
|
2364
3189
|
if (settled)
|
|
2365
3190
|
return;
|
|
2366
3191
|
settled = true;
|
|
3192
|
+
cancelTimer?.();
|
|
2367
3193
|
complete(null, reason, fault);
|
|
2368
3194
|
});
|
|
2369
3195
|
};
|
|
2370
3196
|
const attempt = () => {
|
|
2371
3197
|
if (settled || closing)
|
|
2372
3198
|
return;
|
|
3199
|
+
if (deadline !== undefined && deadline <= this.trace.virtualTimeMs) {
|
|
3200
|
+
reject("TimedOut");
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
2373
3203
|
const role = "id" in target ? this.roles.get(target.id) : undefined;
|
|
2374
3204
|
if (role && this.rolePending(role)) {
|
|
2375
3205
|
role.waiting.push(attempt);
|
|
@@ -2383,6 +3213,9 @@ class ExecutorV1 {
|
|
|
2383
3213
|
const handler = actor.layout.handlers.find((handler) => handler.message_id === protocol.message);
|
|
2384
3214
|
if (!handler)
|
|
2385
3215
|
throw new Error(`actor ${actor.id} has no message ${protocol.message}`);
|
|
3216
|
+
selectedTarget?.(actor);
|
|
3217
|
+
if (settled || closing)
|
|
3218
|
+
return;
|
|
2386
3219
|
const full = actor.layout.mailbox_capacity !== undefined &&
|
|
2387
3220
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
2388
3221
|
if (full) {
|
|
@@ -2422,29 +3255,164 @@ class ExecutorV1 {
|
|
|
2422
3255
|
}
|
|
2423
3256
|
admitted = true;
|
|
2424
3257
|
receiver = actor;
|
|
2425
|
-
|
|
3258
|
+
message = {
|
|
3259
|
+
caller,
|
|
2426
3260
|
handler,
|
|
2427
3261
|
payload,
|
|
2428
3262
|
complete: finish,
|
|
2429
3263
|
reply: true,
|
|
2430
3264
|
obligated: true,
|
|
2431
|
-
|
|
3265
|
+
requestDeadline: deadline,
|
|
3266
|
+
};
|
|
3267
|
+
actor.mailbox.push(message);
|
|
2432
3268
|
this.trace.snapshot("actor.ask", {
|
|
2433
3269
|
actor_id: actor.id,
|
|
2434
3270
|
handler: handler.name,
|
|
2435
3271
|
});
|
|
2436
3272
|
this.dispatchActor(actor);
|
|
2437
3273
|
};
|
|
3274
|
+
const withdrawQueued = () => {
|
|
3275
|
+
if (!message || !receiver)
|
|
3276
|
+
return;
|
|
3277
|
+
const index = receiver.mailbox.indexOf(message);
|
|
3278
|
+
if (index < 0)
|
|
3279
|
+
return;
|
|
3280
|
+
receiver.mailbox.splice(index, 1);
|
|
3281
|
+
admitted = false;
|
|
3282
|
+
for (const admit of receiver.admission.splice(0))
|
|
3283
|
+
admit();
|
|
3284
|
+
};
|
|
3285
|
+
if (deadline !== undefined) {
|
|
3286
|
+
cancelTimer = this.scheduler.after(BigInt(Math.max(0, Math.round((deadline - this.trace.virtualTimeMs) * 1_000_000))), () => {
|
|
3287
|
+
if (settled || closing)
|
|
3288
|
+
return;
|
|
3289
|
+
withdrawQueued();
|
|
3290
|
+
if (admitted)
|
|
3291
|
+
finish(null, "TimedOut");
|
|
3292
|
+
else
|
|
3293
|
+
reject("TimedOut");
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
2438
3296
|
attempt();
|
|
2439
3297
|
return (done) => {
|
|
2440
3298
|
const wasSettled = settled;
|
|
2441
3299
|
settled = true;
|
|
3300
|
+
cancelTimer?.();
|
|
3301
|
+
withdrawQueued();
|
|
2442
3302
|
if (admitted || (wasSettled && !closing))
|
|
2443
3303
|
done(null);
|
|
2444
3304
|
else
|
|
2445
3305
|
closeRequest(done);
|
|
2446
3306
|
};
|
|
2447
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
|
+
}
|
|
2448
3416
|
dispatchActor(actor) {
|
|
2449
3417
|
if (actor.busy || !actor.alive || actor.drainingMailbox)
|
|
2450
3418
|
return;
|
|
@@ -2480,15 +3448,35 @@ class ExecutorV1 {
|
|
|
2480
3448
|
this.dispatchActor(actor);
|
|
2481
3449
|
return;
|
|
2482
3450
|
}
|
|
2483
|
-
const message = actor.mailbox
|
|
3451
|
+
const message = actor.mailbox[0];
|
|
2484
3452
|
if (!message || !actor.alive) {
|
|
2485
3453
|
actor.busy = false;
|
|
2486
3454
|
if (actor.closing && actor.alive)
|
|
2487
3455
|
this.dispatchActor(actor);
|
|
2488
3456
|
return;
|
|
2489
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();
|
|
2490
3466
|
for (const admit of actor.admission.splice(0))
|
|
2491
3467
|
admit();
|
|
3468
|
+
if (message.requestDeadline !== undefined &&
|
|
3469
|
+
message.requestDeadline <= this.trace.virtualTimeMs) {
|
|
3470
|
+
this.closeValueAsync({ kind: "record", typeId: "", fields: message.payload }, null, (fault) => {
|
|
3471
|
+
message.complete(null, "TimedOut");
|
|
3472
|
+
actor.busy = false;
|
|
3473
|
+
if (fault)
|
|
3474
|
+
this.crashActor(actor, fault);
|
|
3475
|
+
else
|
|
3476
|
+
this.dispatchActor(actor);
|
|
3477
|
+
}, actor);
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
2492
3480
|
this.trace.snapshot("actor.receive", {
|
|
2493
3481
|
actor_id: actor.id,
|
|
2494
3482
|
handler: message.handler.name,
|
|
@@ -2496,12 +3484,15 @@ class ExecutorV1 {
|
|
|
2496
3484
|
const frame = this.activate(this.functionAt(message.handler.callable), [actor.state, ...message.payload], null, null, undefined, null, {
|
|
2497
3485
|
id: actor.id,
|
|
2498
3486
|
actor,
|
|
3487
|
+
caller: message.caller,
|
|
3488
|
+
requestDeadline: message.requestDeadline,
|
|
3489
|
+
reply: message.reply,
|
|
2499
3490
|
returned: (value) => {
|
|
2500
3491
|
actor.active = null;
|
|
2501
3492
|
const drained = (fault) => {
|
|
2502
3493
|
actor.busy = false;
|
|
2503
3494
|
if (fault)
|
|
2504
|
-
this.crashActor(actor, fault);
|
|
3495
|
+
this.crashActor(actor, fault, message.handler);
|
|
2505
3496
|
else
|
|
2506
3497
|
this.dispatchActor(actor);
|
|
2507
3498
|
};
|
|
@@ -2517,8 +3508,9 @@ class ExecutorV1 {
|
|
|
2517
3508
|
else {
|
|
2518
3509
|
this.crashActor(actor, {
|
|
2519
3510
|
kind: "panic",
|
|
3511
|
+
handlerFailed: true,
|
|
2520
3512
|
message: "unhandled declared handler failure",
|
|
2521
|
-
});
|
|
3513
|
+
}, message.handler);
|
|
2522
3514
|
return;
|
|
2523
3515
|
}
|
|
2524
3516
|
}
|
|
@@ -2533,12 +3525,19 @@ class ExecutorV1 {
|
|
|
2533
3525
|
failed: (fault) => {
|
|
2534
3526
|
actor.active = null;
|
|
2535
3527
|
actor.busy = false;
|
|
2536
|
-
if (fault.kind === "
|
|
3528
|
+
if (fault.kind === "trap" &&
|
|
3529
|
+
fault.trap === "actor_refused" &&
|
|
3530
|
+
!fault.secondary?.length) {
|
|
3531
|
+
actor.crashing = null;
|
|
3532
|
+
message.complete(null, "Refused");
|
|
3533
|
+
this.dispatchActor(actor);
|
|
3534
|
+
}
|
|
3535
|
+
else if (fault.kind === "panic" && fault.cancelled) {
|
|
2537
3536
|
message.complete(null, "Dead");
|
|
2538
3537
|
this.dispatchActor(actor);
|
|
2539
3538
|
}
|
|
2540
3539
|
else {
|
|
2541
|
-
this.crashActor(actor, fault);
|
|
3540
|
+
this.crashActor(actor, fault, message.handler);
|
|
2542
3541
|
message.complete(null, "Trapped");
|
|
2543
3542
|
}
|
|
2544
3543
|
},
|
|
@@ -2547,7 +3546,12 @@ class ExecutorV1 {
|
|
|
2547
3546
|
this.runFrame(frame);
|
|
2548
3547
|
});
|
|
2549
3548
|
}
|
|
2550
|
-
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
|
+
}
|
|
2551
3555
|
const debt = ++this.nextFaultDebt;
|
|
2552
3556
|
this.faultDebts.add(debt);
|
|
2553
3557
|
if (actor.supervisor)
|
|
@@ -2555,6 +3559,7 @@ class ExecutorV1 {
|
|
|
2555
3559
|
actor.crashing ??= fault;
|
|
2556
3560
|
actor.alive = false;
|
|
2557
3561
|
actor.busy = false;
|
|
3562
|
+
this.cancelActorTimers(actor);
|
|
2558
3563
|
this.trace.snapshot("actor.crash", {
|
|
2559
3564
|
actor_id: actor.id,
|
|
2560
3565
|
message: this.faultText(fault),
|
|
@@ -2564,47 +3569,114 @@ class ExecutorV1 {
|
|
|
2564
3569
|
actor.mailboxDrained.push(() => finished(action));
|
|
2565
3570
|
return;
|
|
2566
3571
|
}
|
|
2567
|
-
this.closeValueAsync(actor.state, this.faultText(fault), () =>
|
|
2568
|
-
actor.
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
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);
|
|
2586
3609
|
};
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
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
|
+
});
|
|
2608
3680
|
}
|
|
2609
3681
|
supervisorFor(value) {
|
|
2610
3682
|
if (value.kind !== "supervisor")
|
|
@@ -2657,6 +3729,23 @@ class ExecutorV1 {
|
|
|
2657
3729
|
return (!!owner?.alive &&
|
|
2658
3730
|
(owner.recovering.has(role.child) || owner.children[role.child] === null));
|
|
2659
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
|
+
}
|
|
2660
3749
|
wakeRoles() {
|
|
2661
3750
|
for (const role of this.roles.values()) {
|
|
2662
3751
|
this.roleValue(role);
|
|
@@ -2789,6 +3878,16 @@ class ExecutorV1 {
|
|
|
2789
3878
|
else if (value.kind === "supervisor")
|
|
2790
3879
|
this.stopSupervisor(this.supervisorFor(value), true);
|
|
2791
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
|
+
}
|
|
2792
3891
|
stopSupervisor(owner, force = false) {
|
|
2793
3892
|
if (force) {
|
|
2794
3893
|
owner.terminating = true;
|
|
@@ -2808,12 +3907,7 @@ class ExecutorV1 {
|
|
|
2808
3907
|
const index = childIndex--;
|
|
2809
3908
|
const child = owner.children[index];
|
|
2810
3909
|
owner.teardownChild = child;
|
|
2811
|
-
const
|
|
2812
|
-
const stopNs = spec.stop_ns === undefined
|
|
2813
|
-
? "actor" in spec.role
|
|
2814
|
-
? 5_000_000_000
|
|
2815
|
-
: null
|
|
2816
|
-
: spec.stop_ns;
|
|
3910
|
+
const stopNs = this.childStopBudget(owner, index);
|
|
2817
3911
|
if (!owner.terminating && stopNs !== null && stopNs > 0) {
|
|
2818
3912
|
owner.teardownCancelTimer = this.scheduler.after(BigInt(stopNs), () => this.terminateChild(child));
|
|
2819
3913
|
}
|
|
@@ -2821,9 +3915,11 @@ class ExecutorV1 {
|
|
|
2821
3915
|
return;
|
|
2822
3916
|
}
|
|
2823
3917
|
this.closeValueAsync({ kind: "record", typeId: "", fields: owner.config }, null, (fault) => {
|
|
2824
|
-
|
|
2825
|
-
|
|
3918
|
+
const debt = fault ? ++this.nextFaultDebt : undefined;
|
|
3919
|
+
if (debt !== undefined)
|
|
3920
|
+
this.faultDebts.add(debt);
|
|
2826
3921
|
owner.completed = true;
|
|
3922
|
+
this.settleSupervisedEnd(owner, fault ? "Crashed" : owner.terminating ? "Terminated" : "Stopped", debt === undefined ? [] : [debt]);
|
|
2827
3923
|
for (const wake of owner.closed.splice(0))
|
|
2828
3924
|
wake();
|
|
2829
3925
|
this.wakeRoles();
|
|
@@ -2858,6 +3954,15 @@ class ExecutorV1 {
|
|
|
2858
3954
|
if (actor.closing || actor.completed)
|
|
2859
3955
|
return;
|
|
2860
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
|
+
}
|
|
2861
3966
|
// Admission closes immediately. The active turn and every accepted queued
|
|
2862
3967
|
// turn finish before stop hooks and state release.
|
|
2863
3968
|
this.dispatchActor(actor);
|
|
@@ -2869,8 +3974,17 @@ class ExecutorV1 {
|
|
|
2869
3974
|
return;
|
|
2870
3975
|
actor.closing = true;
|
|
2871
3976
|
actor.terminating = true;
|
|
2872
|
-
|
|
2873
|
-
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
|
+
}
|
|
2874
3988
|
this.dispatchActor(actor);
|
|
2875
3989
|
for (const admit of actor.admission.splice(0))
|
|
2876
3990
|
admit();
|
|
@@ -2881,11 +3995,11 @@ class ExecutorV1 {
|
|
|
2881
3995
|
const next = () => {
|
|
2882
3996
|
const hook = actor.terminating ? undefined : hooks.shift();
|
|
2883
3997
|
if (hook !== undefined) {
|
|
2884
|
-
actor.
|
|
2885
|
-
actor.
|
|
3998
|
+
actor.terminalHook = this.invokeFrame(actor, hook, [actor.state], () => {
|
|
3999
|
+
actor.terminalHook = null;
|
|
2886
4000
|
next();
|
|
2887
4001
|
}, (fault) => {
|
|
2888
|
-
actor.
|
|
4002
|
+
actor.terminalHook = null;
|
|
2889
4003
|
if (!(actor.terminating && fault.kind === "panic" && fault.cancelled))
|
|
2890
4004
|
actor.closingFault = combineFaults(actor.closingFault, fault);
|
|
2891
4005
|
hooks.length = 0;
|
|
@@ -2895,31 +4009,37 @@ class ExecutorV1 {
|
|
|
2895
4009
|
else {
|
|
2896
4010
|
this.closeValueAsync(actor.state, this.faultText(actor.closingFault), (fault) => {
|
|
2897
4011
|
actor.closingFault = combineFaults(actor.closingFault, fault);
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
actor.
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
: actor.
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
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
|
+
});
|
|
2916
4036
|
}, actor);
|
|
2917
4037
|
}
|
|
2918
4038
|
};
|
|
2919
4039
|
next();
|
|
2920
4040
|
}
|
|
2921
|
-
settleSupervisedEnd(
|
|
2922
|
-
const parent =
|
|
4041
|
+
settleSupervisedEnd(subject, reason, debts) {
|
|
4042
|
+
const parent = subject.supervisor;
|
|
2923
4043
|
if (!parent ||
|
|
2924
4044
|
!parent.owner.alive ||
|
|
2925
4045
|
parent.owner.recovering.has(parent.child))
|
|
@@ -2928,7 +4048,7 @@ class ExecutorV1 {
|
|
|
2928
4048
|
const policy = owner.specs[child].restart;
|
|
2929
4049
|
if (policy === "permanent" ||
|
|
2930
4050
|
(policy === "transient" && reason !== "Stopped")) {
|
|
2931
|
-
this.restartChild(owner, child,
|
|
4051
|
+
this.restartChild(owner, child, debts);
|
|
2932
4052
|
}
|
|
2933
4053
|
else {
|
|
2934
4054
|
owner.spent.add(child);
|
|
@@ -2936,6 +4056,16 @@ class ExecutorV1 {
|
|
|
2936
4056
|
}
|
|
2937
4057
|
}
|
|
2938
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
|
+
}
|
|
2939
4069
|
haltWithFault(fault) {
|
|
2940
4070
|
const isPanic = fault.kind === "panic";
|
|
2941
4071
|
const status = isPanic ? "panic" : "trap";
|
|
@@ -2945,7 +4075,7 @@ class ExecutorV1 {
|
|
|
2945
4075
|
: (fault.message ?? trapMessage(fault.trap));
|
|
2946
4076
|
const [code, kind] = faultInfo(fault);
|
|
2947
4077
|
// Native writes the failure line to stderr before it exits; so does this.
|
|
2948
|
-
this.
|
|
4078
|
+
this.reportFault(fault);
|
|
2949
4079
|
// A failing run reports no exit code: native exits 1 for every fault and
|
|
2950
4080
|
// names the kind in its message, so the kind is what travels, in
|
|
2951
4081
|
// `runtime_failures`. The page turns it into an exit code of its own.
|
|
@@ -2975,14 +4105,22 @@ function combineFaults(primary, secondary) {
|
|
|
2975
4105
|
// Reports own an immutable ordered view of original diagnostics. Faults can
|
|
2976
4106
|
// be shared by a parked unwind, actor completion and multiple observers;
|
|
2977
4107
|
// combining those views must neither duplicate nor mutate their graph.
|
|
2978
|
-
const
|
|
2979
|
-
|
|
4108
|
+
const control = (fault) => (fault.kind === "panic" && fault.cancelled) ||
|
|
4109
|
+
(fault.kind === "trap" && fault.trap === "actor_refused");
|
|
4110
|
+
const retained = control(primary)
|
|
4111
|
+
? diagnostics.filter((fault) => !control(fault))
|
|
2980
4112
|
: diagnostics;
|
|
2981
4113
|
const [first, ...rest] = retained.length ? retained : diagnostics;
|
|
2982
4114
|
return { ...first, primary: first, secondary: rest };
|
|
2983
4115
|
}
|
|
2984
4116
|
function faultInfo(fault) {
|
|
2985
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"];
|
|
2986
4124
|
if (fault.cancelled)
|
|
2987
4125
|
return [-1, "Cancelled"];
|
|
2988
4126
|
if (fault.deadline)
|
|
@@ -2994,13 +4132,21 @@ function faultInfo(fault) {
|
|
|
2994
4132
|
divide_by_zero: [202, "DivideByZero"],
|
|
2995
4133
|
shift_out_of_range: [204, "ShiftOutOfRange"],
|
|
2996
4134
|
vector_bounds: [205, "IndexOutOfBounds"],
|
|
4135
|
+
actor_refused: [-4, "Refused"],
|
|
2997
4136
|
}[fault.trap];
|
|
2998
4137
|
}
|
|
2999
|
-
function
|
|
4138
|
+
function faultDiagnostic(fault, secondary = false) {
|
|
3000
4139
|
const [code, reason] = faultInfo(fault);
|
|
3001
4140
|
const label = secondary ? "secondary failure" : "failure";
|
|
3002
|
-
const message = fault.message === undefined
|
|
3003
|
-
|
|
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) +
|
|
3004
4150
|
(fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
|
|
3005
4151
|
}
|
|
3006
4152
|
// ── refs ────────────────────────────────────────────────────────────────────
|
|
@@ -3063,6 +4209,8 @@ function actorErrorRole(reason) {
|
|
|
3063
4209
|
return "ActorErrorFailed";
|
|
3064
4210
|
case "Trapped":
|
|
3065
4211
|
return "ActorErrorTrapped";
|
|
4212
|
+
case "Refused":
|
|
4213
|
+
return "ActorErrorRefused";
|
|
3066
4214
|
case "Dead":
|
|
3067
4215
|
return "ActorErrorDead";
|
|
3068
4216
|
case "TimedOut":
|
|
@@ -3105,12 +4253,17 @@ function sendErrorRole(reason) {
|
|
|
3105
4253
|
throw new Error(`unknown send error reason ${reason}`);
|
|
3106
4254
|
}
|
|
3107
4255
|
}
|
|
3108
|
-
function calleeFunction(value) {
|
|
4256
|
+
function calleeFunction(value, specialization = null) {
|
|
3109
4257
|
switch (value.kind) {
|
|
3110
4258
|
case "function":
|
|
3111
4259
|
return Number(value.id);
|
|
3112
|
-
case "closure":
|
|
3113
|
-
|
|
4260
|
+
case "closure": {
|
|
4261
|
+
const selected = specialization ?? value.specialization;
|
|
4262
|
+
const entry = value.entries.find((candidate) => candidate.specialization === selected);
|
|
4263
|
+
if (!entry)
|
|
4264
|
+
throw new Error("callable has no selected concrete invocation entry");
|
|
4265
|
+
return entry.body;
|
|
4266
|
+
}
|
|
3114
4267
|
default:
|
|
3115
4268
|
throw new TypeError(`indirect.call expected a callable value, got ${value.kind}`);
|
|
3116
4269
|
}
|
|
@@ -3147,7 +4300,9 @@ function toComparable(value) {
|
|
|
3147
4300
|
case "vector":
|
|
3148
4301
|
return value.items.map(toComparable);
|
|
3149
4302
|
case "rc":
|
|
3150
|
-
return
|
|
4303
|
+
return "Rc";
|
|
4304
|
+
case "weak":
|
|
4305
|
+
return "Weak";
|
|
3151
4306
|
case "map":
|
|
3152
4307
|
return [...value.entries.values()].map((entry) => [
|
|
3153
4308
|
toComparable(entry.key),
|
|
@@ -3187,6 +4342,8 @@ function trapMessage(trap) {
|
|
|
3187
4342
|
return "shift amount out of range";
|
|
3188
4343
|
case "vector_bounds":
|
|
3189
4344
|
return "index out of bounds";
|
|
4345
|
+
case "actor_refused":
|
|
4346
|
+
return "delivery refused by interceptor";
|
|
3190
4347
|
}
|
|
3191
4348
|
}
|
|
3192
4349
|
/// The operand type the package records. `isize` and `usize` are 64-bit in the
|
|
@@ -3318,7 +4475,7 @@ function applyBinary(op, lhs, rhs, ty) {
|
|
|
3318
4475
|
};
|
|
3319
4476
|
}
|
|
3320
4477
|
if (lhs.kind === "string") {
|
|
3321
|
-
return
|
|
4478
|
+
return stringValue(lhs.value + asString(rhs));
|
|
3322
4479
|
}
|
|
3323
4480
|
return {
|
|
3324
4481
|
kind: "i64",
|
|
@@ -3384,7 +4541,7 @@ function applyCast(value, to) {
|
|
|
3384
4541
|
return { kind: "bool", value: truth(value) };
|
|
3385
4542
|
case "char":
|
|
3386
4543
|
case "string":
|
|
3387
|
-
return
|
|
4544
|
+
return stringValue(asString(value));
|
|
3388
4545
|
default:
|
|
3389
4546
|
break;
|
|
3390
4547
|
}
|