@hew-lang/sandbox-vm 0.9.2 → 0.10.0-rc1
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/dist/interpreter/run-program.js +1 -0
- package/dist/interpreter/strings.d.ts +6 -0
- package/dist/interpreter/strings.js +8 -0
- package/dist/interpreter/types.d.ts +1 -1
- package/dist/interpreter/v1/encoding-storage.d.ts +33 -0
- package/dist/interpreter/v1/encoding-storage.js +113 -0
- package/dist/interpreter/v1/encoding-values.js +28 -18
- package/dist/interpreter/v1/exec.js +1368 -90
- package/dist/interpreter/v1/instructions.generated.d.ts +94 -6
- package/dist/interpreter/v1/instructions.generated.js +1 -0
- package/dist/interpreter/v1/managed-budget.d.ts +88 -0
- package/dist/interpreter/v1/managed-budget.js +556 -0
- package/dist/interpreter/v1/package-graph.js +77 -2
- package/dist/interpreter/v1/package-validator.generated.js +20572 -15516
- package/dist/interpreter/v1/package.d.ts +4 -2
- package/dist/interpreter/v1/package.js +6 -0
- package/dist/interpreter/v1/pipes.d.ts +24 -1
- package/dist/interpreter/v1/pipes.js +91 -8
- package/dist/interpreter/v1/shims.d.ts +1 -0
- package/dist/interpreter/v1/shims.js +54 -28
- package/dist/interpreter/v1/validate.js +2 -1
- package/dist/interpreter/v1/value-operations.d.ts +3 -1
- package/dist/interpreter/v1/value-operations.js +23 -10
- package/dist/interpreter/values.d.ts +36 -11
- package/dist/interpreter/values.js +118 -6
- package/package.json +4 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BrowserDirectories, } from "./browser-directory.js";
|
|
2
|
+
import { encodingStorage, releaseEncoding } from "./encoding-storage.js";
|
|
2
3
|
/// The `hew.sandbox.bytecode.v1` executor.
|
|
3
4
|
///
|
|
4
5
|
/// The package is a projection of verified ownership SIR, so this executor
|
|
@@ -10,8 +11,8 @@ import { executeCodec } from "./codecs.js";
|
|
|
10
11
|
import { actorIdentical } from "./actor-identity.js";
|
|
11
12
|
import { ActorWaits } from "./actor-waits.js";
|
|
12
13
|
import { TraceBuilder, runtimeFailure } from "../trace.js";
|
|
13
|
-
import { UNIT, cloneValue, stringValue, toJsonValue, } from "../values.js";
|
|
14
|
-
import { runtimeTag } from "./package.js";
|
|
14
|
+
import { UNIT, cloneValue, releaseVector, byteVectorValue, vectorStorage, stringValue, toJsonValue, } from "../values.js";
|
|
15
|
+
import { runtimeTag, staticOperations } from "./package.js";
|
|
15
16
|
import { StructuralRenderer } from "./structural.js";
|
|
16
17
|
import { timestampBinary } from "./timestamp.js";
|
|
17
18
|
import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
|
|
@@ -22,6 +23,7 @@ import { DeterministicIds } from "../../scheduler/ids.js";
|
|
|
22
23
|
import { Mt19937 } from "./mt19937.js";
|
|
23
24
|
import { ShimFault, CODEC_REGISTRATION, decodeUtf8Checked, resolveExtern, resolveRuntime, resolveSuspension, } from "./shims.js";
|
|
24
25
|
import { AdmittedPackage } from "./validate.js";
|
|
26
|
+
import { HeapExceeded, ManagedBudget, ManagedLayouts, } from "./managed-budget.js";
|
|
25
27
|
const DEFAULT_STEP_BUDGET = 1_000_000;
|
|
26
28
|
const DEFAULT_ACTOR_STOP_NS = 5_000_000_000;
|
|
27
29
|
/// Ends the run. The status is already recorded on the trace.
|
|
@@ -119,6 +121,7 @@ class ExecutorV1 {
|
|
|
119
121
|
tasks = new Map();
|
|
120
122
|
completedTasks = 0;
|
|
121
123
|
actorIdentity = 0n;
|
|
124
|
+
arenaIdentity = 0n;
|
|
122
125
|
faultDebts = new Set();
|
|
123
126
|
nextFaultDebt = 0;
|
|
124
127
|
closingResources = new WeakSet();
|
|
@@ -128,6 +131,7 @@ class ExecutorV1 {
|
|
|
128
131
|
terminating = false;
|
|
129
132
|
rootFault = null;
|
|
130
133
|
pkg;
|
|
134
|
+
managed;
|
|
131
135
|
directories;
|
|
132
136
|
semaphores;
|
|
133
137
|
constructor(admission, trace, stdin, policy, options) {
|
|
@@ -136,12 +140,14 @@ class ExecutorV1 {
|
|
|
136
140
|
this.options = options;
|
|
137
141
|
const pkg = admission.bytecode;
|
|
138
142
|
this.pkg = pkg;
|
|
143
|
+
this.managed = new ManagedLayouts(pkg.managed_types ?? [], (value, type) => this.managedRetained(value, type));
|
|
139
144
|
this.directories = new BrowserDirectories(options.directoryRoots);
|
|
140
145
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
141
146
|
this.semaphores = new Semaphores((nanos, ready) => this.scheduler.after(nanos, ready));
|
|
142
147
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
143
148
|
this.host = {
|
|
144
149
|
argv: [...(options.argv ?? ["main.hew"])],
|
|
150
|
+
arenaInstanceId: () => this.arenaIdentity === 0x7fffffffffffffffn ? 0n : ++this.arenaIdentity,
|
|
145
151
|
unixTimeMillis: () => this.trace.virtualTimeMs,
|
|
146
152
|
monotonicTimeNanos: () => {
|
|
147
153
|
const millis = this.trace.virtualTimeMs;
|
|
@@ -189,6 +195,7 @@ class ExecutorV1 {
|
|
|
189
195
|
pending: null,
|
|
190
196
|
fault: null,
|
|
191
197
|
receiving: false,
|
|
198
|
+
observers: new Set(),
|
|
192
199
|
});
|
|
193
200
|
return { kind: "stream", channelId: id };
|
|
194
201
|
},
|
|
@@ -226,12 +233,13 @@ class ExecutorV1 {
|
|
|
226
233
|
this.current = this.activate(this.functionAt(pkg.entry.function), [], null, null, undefined, null, {
|
|
227
234
|
id: "actor:root",
|
|
228
235
|
actor: null,
|
|
229
|
-
returned: (value) => {
|
|
236
|
+
returned: (value, released) => {
|
|
230
237
|
this.completeRoot();
|
|
231
238
|
this.trace.exitCode =
|
|
232
239
|
pkg.entry?.exit === "status" && value.kind === "i64"
|
|
233
240
|
? Number(value.value)
|
|
234
241
|
: 0;
|
|
242
|
+
released();
|
|
235
243
|
},
|
|
236
244
|
failed: (fault) => {
|
|
237
245
|
this.rootFault = fault;
|
|
@@ -288,6 +296,7 @@ class ExecutorV1 {
|
|
|
288
296
|
this.running = true;
|
|
289
297
|
while (this.running) {
|
|
290
298
|
const act = this.current;
|
|
299
|
+
act.context.activeFrame = act;
|
|
291
300
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
292
301
|
if (this.scopeDeadline(act, fault) !== fault)
|
|
293
302
|
act.context.pendingCancellation ??= fault;
|
|
@@ -337,29 +346,42 @@ class ExecutorV1 {
|
|
|
337
346
|
: parentContext;
|
|
338
347
|
const env = new Map();
|
|
339
348
|
for (const [at, param] of fn.params.entries()) {
|
|
340
|
-
env.set(param.value, ownedRef(args[at] ?? UNIT));
|
|
349
|
+
env.set(param.value, ownedRef(args[at] ?? UNIT, param.own === "owned" ? executionContext.actor?.budget : undefined, param.managed_type ?? undefined));
|
|
341
350
|
}
|
|
342
351
|
const places = new Map();
|
|
343
352
|
const trivial = new Set();
|
|
353
|
+
const owned = new Set();
|
|
354
|
+
const managedTypes = new Map();
|
|
344
355
|
const record = (value) => {
|
|
345
356
|
if (value.own === "none")
|
|
346
357
|
trivial.add(value.value);
|
|
358
|
+
if (value.own === "owned")
|
|
359
|
+
owned.add(value.value);
|
|
360
|
+
if (value.managed_type != null)
|
|
361
|
+
managedTypes.set(value.value, value.managed_type);
|
|
347
362
|
};
|
|
348
363
|
fn.params.forEach(record);
|
|
349
364
|
for (const block of fn.blocks) {
|
|
350
365
|
block.params.forEach(record);
|
|
351
|
-
for (const op of block
|
|
366
|
+
for (const op of staticOperations(block)) {
|
|
367
|
+
op.definitions?.forEach(record);
|
|
352
368
|
if ("dst" in op && op.dst != null && op.own === "none")
|
|
353
369
|
trivial.add(op.dst);
|
|
354
370
|
}
|
|
355
371
|
const term = block.term;
|
|
356
372
|
if ("result" in term && term.result && term.result !== "never")
|
|
357
373
|
record(term.result);
|
|
374
|
+
if (term.op === "call" && term.handback)
|
|
375
|
+
record(term.handback);
|
|
358
376
|
if (term.op === "switch.variant")
|
|
359
377
|
for (const arm of term.arms)
|
|
360
378
|
arm.fields.forEach(record);
|
|
361
379
|
}
|
|
380
|
+
for (const [index, param] of fn.params.entries())
|
|
381
|
+
if (param.managed_type != null && args[index])
|
|
382
|
+
this.stampManaged(args[index], param.managed_type, executionContext.actor);
|
|
362
383
|
return {
|
|
384
|
+
accountActor: executionContext.actor,
|
|
363
385
|
rcOwner: fn.actor_construction
|
|
364
386
|
? { blocks: new Set(), closed: false }
|
|
365
387
|
: (caller?.rcOwner ??
|
|
@@ -372,6 +394,11 @@ class ExecutorV1 {
|
|
|
372
394
|
opIndex: 0,
|
|
373
395
|
env,
|
|
374
396
|
trivial,
|
|
397
|
+
owned,
|
|
398
|
+
managedTypes,
|
|
399
|
+
managedInputs: [],
|
|
400
|
+
managedCleanup: new Set(),
|
|
401
|
+
managedChildren: new Set(),
|
|
375
402
|
scopes: new Map(),
|
|
376
403
|
places,
|
|
377
404
|
fault: null,
|
|
@@ -414,7 +441,11 @@ class ExecutorV1 {
|
|
|
414
441
|
}
|
|
415
442
|
read(act, id) {
|
|
416
443
|
try {
|
|
417
|
-
|
|
444
|
+
const value = readRef(this.refOf(act, id));
|
|
445
|
+
const type = act.managedTypes.get(id);
|
|
446
|
+
if (type != null)
|
|
447
|
+
this.stampManaged(value, type, act.context.actor);
|
|
448
|
+
return value;
|
|
418
449
|
}
|
|
419
450
|
catch (error) {
|
|
420
451
|
throw new Error(`${act.fn.name} block ${act.block.id} value ${id}: ${error.message}`);
|
|
@@ -424,7 +455,414 @@ class ExecutorV1 {
|
|
|
424
455
|
if (id === null || id === undefined) {
|
|
425
456
|
return;
|
|
426
457
|
}
|
|
427
|
-
act.
|
|
458
|
+
const type = act.managedTypes.get(id);
|
|
459
|
+
if (type != null)
|
|
460
|
+
this.stampManaged(value, type, act.context.actor);
|
|
461
|
+
const budget = act.owned.has(id) ? act.context.actor?.budget : undefined;
|
|
462
|
+
act.env.set(id, ownedRef(value, budget, type));
|
|
463
|
+
}
|
|
464
|
+
stampManaged(value, type, actor) {
|
|
465
|
+
const budget = actor?.budget;
|
|
466
|
+
this.managed.stamp(value, type);
|
|
467
|
+
if (value.kind === "rc" &&
|
|
468
|
+
!value.closed &&
|
|
469
|
+
value.cell?.owner &&
|
|
470
|
+
budget &&
|
|
471
|
+
!value.cell.managedLease) {
|
|
472
|
+
const row = this.managed.row(type);
|
|
473
|
+
if (row.kind !== "shared")
|
|
474
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
475
|
+
value.cell.managedPayload = row.payload;
|
|
476
|
+
value.cell.managedLease = budget.retain({
|
|
477
|
+
value: { kind: "rc", cell: value.cell, closed: false },
|
|
478
|
+
type,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (value.kind === "task") {
|
|
482
|
+
const task = this.taskFor(value);
|
|
483
|
+
task.managedType = type;
|
|
484
|
+
task.handles.add(value);
|
|
485
|
+
}
|
|
486
|
+
if (value.kind === "stream" && !this.generatorStreams.has(value.channelId))
|
|
487
|
+
this.bindManagedStream(value, type, actor);
|
|
488
|
+
if (value.kind === "generator")
|
|
489
|
+
this.generatorFor(value).managedType = type;
|
|
490
|
+
if (value.kind === "encoding") {
|
|
491
|
+
value.prepareMutation = budget
|
|
492
|
+
? (replacement, detached) => budget.checkMutation(value, replacement, detached)
|
|
493
|
+
: undefined;
|
|
494
|
+
}
|
|
495
|
+
else if (value.kind === "string") {
|
|
496
|
+
value.prepareGrowth = budget
|
|
497
|
+
? (capacity, detached) => budget.checkStringGrowth(value, capacity, detached)
|
|
498
|
+
: undefined;
|
|
499
|
+
}
|
|
500
|
+
else if (value.kind === "map") {
|
|
501
|
+
value.prepareInsert = budget
|
|
502
|
+
? (staged, slot, key, item, copyKey) => {
|
|
503
|
+
const row = this.managed.row(type);
|
|
504
|
+
if (row.kind !== "map" && row.kind !== "set")
|
|
505
|
+
throw new Error("map has no canonical entry descriptor");
|
|
506
|
+
const entries = new Map(staged.entries);
|
|
507
|
+
entries.set(slot, {
|
|
508
|
+
key: copyKey
|
|
509
|
+
? this.managed.copy(key, row.kind === "map" ? row.key : row.element)
|
|
510
|
+
: key,
|
|
511
|
+
value: item,
|
|
512
|
+
});
|
|
513
|
+
budget.checkMutation(value, { ...staged, entries });
|
|
514
|
+
}
|
|
515
|
+
: undefined;
|
|
516
|
+
}
|
|
517
|
+
else if (value.kind === "vector") {
|
|
518
|
+
value.prepareMutation = budget
|
|
519
|
+
? (capacity, detached, appended) => {
|
|
520
|
+
const row = this.managed.row(type);
|
|
521
|
+
const storage = vectorStorage(value);
|
|
522
|
+
let items = detached &&
|
|
523
|
+
(row.kind === "vector" ||
|
|
524
|
+
row.kind === "array" ||
|
|
525
|
+
row.kind === "slice")
|
|
526
|
+
? value.items.map((item) => this.managed.copy(item, row.element))
|
|
527
|
+
: value.items;
|
|
528
|
+
if (appended?.length) {
|
|
529
|
+
if (row.kind !== "vector" &&
|
|
530
|
+
row.kind !== "array" &&
|
|
531
|
+
row.kind !== "slice")
|
|
532
|
+
throw new Error("vector append lacks its canonical element type");
|
|
533
|
+
items = [
|
|
534
|
+
...items,
|
|
535
|
+
...appended.map((item) => this.managed.copy(item, row.element)),
|
|
536
|
+
];
|
|
537
|
+
}
|
|
538
|
+
budget.checkMutation(value, {
|
|
539
|
+
...value,
|
|
540
|
+
items,
|
|
541
|
+
storage: { ...storage, capacity, items, owners: 1 },
|
|
542
|
+
}, detached);
|
|
543
|
+
}
|
|
544
|
+
: undefined;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
frameManagedRoots(frame) {
|
|
548
|
+
const roots = [];
|
|
549
|
+
for (const [id, ref] of frame.env) {
|
|
550
|
+
const type = frame.managedTypes.get(id);
|
|
551
|
+
if (type != null && frame.owned.has(id) && liveRef(ref))
|
|
552
|
+
roots.push({ value: readRef(ref), type });
|
|
553
|
+
}
|
|
554
|
+
for (const [id, cell] of frame.places) {
|
|
555
|
+
const type = frame.fn.places[id]?.managed_type;
|
|
556
|
+
if (type != null && cell.valid)
|
|
557
|
+
roots.push({ value: cell.value, type });
|
|
558
|
+
}
|
|
559
|
+
for (const scope of frame.scopes.values())
|
|
560
|
+
for (const task of scope.tasks)
|
|
561
|
+
if (task.managedType != null)
|
|
562
|
+
roots.push({
|
|
563
|
+
value: { kind: "task", id: task.id },
|
|
564
|
+
type: task.managedType,
|
|
565
|
+
});
|
|
566
|
+
for (const lease of frame.managedInputs)
|
|
567
|
+
roots.push(...(lease.roots ?? []));
|
|
568
|
+
for (const cleanup of frame.managedCleanup)
|
|
569
|
+
roots.push(...cleanup.roots);
|
|
570
|
+
for (const child of frame.managedChildren)
|
|
571
|
+
roots.push(...this.frameManagedRoots(child.context.activeFrame ?? child));
|
|
572
|
+
if (frame.caller)
|
|
573
|
+
roots.push(...this.frameManagedRoots(frame.caller));
|
|
574
|
+
return roots;
|
|
575
|
+
}
|
|
576
|
+
bindManagedStream(value, type, actor) {
|
|
577
|
+
const row = this.managed.row(type);
|
|
578
|
+
if (row.kind !== "provider" ||
|
|
579
|
+
row.provider !== "stream" ||
|
|
580
|
+
value.kind !== "stream" ||
|
|
581
|
+
this.generatorStreams.has(value.channelId))
|
|
582
|
+
return;
|
|
583
|
+
const budget = actor?.budget;
|
|
584
|
+
this.pipes.bind(value, budget && actor
|
|
585
|
+
? {
|
|
586
|
+
owner: actor,
|
|
587
|
+
acquire: (envelope) => {
|
|
588
|
+
const lease = budget.retain({
|
|
589
|
+
value: envelope.value,
|
|
590
|
+
type: row.children[0],
|
|
591
|
+
slots: true,
|
|
592
|
+
identity: envelope,
|
|
593
|
+
});
|
|
594
|
+
return () => lease.release();
|
|
595
|
+
},
|
|
596
|
+
refused: () => this.failActorBudget(actor),
|
|
597
|
+
}
|
|
598
|
+
: undefined);
|
|
599
|
+
}
|
|
600
|
+
adoptManaged(root, actor) {
|
|
601
|
+
if (!this.managed.needsAdoption(root.type))
|
|
602
|
+
return;
|
|
603
|
+
this.managed.collect(root, new Map(), undefined, undefined, (value, type) => {
|
|
604
|
+
this.bindManagedStream(value, type, actor);
|
|
605
|
+
const row = this.managed.row(type);
|
|
606
|
+
if (value.kind === "rc" && value.cell && !value.closed) {
|
|
607
|
+
if (row.kind !== "shared")
|
|
608
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
609
|
+
const cell = value.cell;
|
|
610
|
+
if (cell.owner !== actor.rcOwner && !cell.dying) {
|
|
611
|
+
cell.owner?.blocks.delete(cell);
|
|
612
|
+
cell.owner = actor.rcOwner;
|
|
613
|
+
cell.managedPayload = row.payload;
|
|
614
|
+
actor.rcOwner.blocks.add(cell);
|
|
615
|
+
cell.managedLease = this.rehomeLease(cell.managedLease, actor.budget);
|
|
616
|
+
if (!cell.managedLease && actor.budget)
|
|
617
|
+
cell.managedLease = actor.budget.retain({ value, type });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else if (row.kind === "generator")
|
|
621
|
+
this.rehomeFrame(this.generatorFor(value).frame, actor);
|
|
622
|
+
else if (value.kind === "task")
|
|
623
|
+
this.rehomeTask(this.taskFor(value), actor);
|
|
624
|
+
}, (value, type) => {
|
|
625
|
+
const row = this.managed.row(type);
|
|
626
|
+
// Encoding trees and opaque host providers cannot contain receiving
|
|
627
|
+
// stream handles. This traversal changes queue ownership only;
|
|
628
|
+
// their payload accounting keeps its separate canonical visitor.
|
|
629
|
+
return row.kind === "provider" &&
|
|
630
|
+
["json", "yaml", "toml", "opaque"].includes(row.provider)
|
|
631
|
+
? { identity: value, bytes: 0n, children: [] }
|
|
632
|
+
: this.managedRetained(value, type);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
rehomeLease(lease, budget) {
|
|
636
|
+
if (!lease?.roots)
|
|
637
|
+
return lease;
|
|
638
|
+
const replacement = this.retainRoots(lease.roots, budget);
|
|
639
|
+
lease.release();
|
|
640
|
+
return replacement;
|
|
641
|
+
}
|
|
642
|
+
retainRoots(roots, budget) {
|
|
643
|
+
// Unbounded frames keep typed ownership metadata for a later generator
|
|
644
|
+
// transfer, without building an allocation graph for an account.
|
|
645
|
+
return budget?.retainMany(roots) ?? { roots, release() { } };
|
|
646
|
+
}
|
|
647
|
+
retainCleanup(frame, roots) {
|
|
648
|
+
const cleanup = {
|
|
649
|
+
roots,
|
|
650
|
+
lease: this.retainRoots(roots, frame.accountActor?.budget),
|
|
651
|
+
};
|
|
652
|
+
frame.managedCleanup.add(cleanup);
|
|
653
|
+
return {
|
|
654
|
+
roots,
|
|
655
|
+
release: () => {
|
|
656
|
+
if (!frame.managedCleanup.delete(cleanup))
|
|
657
|
+
return;
|
|
658
|
+
cleanup.lease.release();
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
rehomeTask(task, actor) {
|
|
663
|
+
const frame = task.frame?.context.activeFrame ?? task.frame;
|
|
664
|
+
if (task.budget === actor.budget && frame?.accountActor === actor)
|
|
665
|
+
return;
|
|
666
|
+
task.budget = actor.budget;
|
|
667
|
+
if (frame)
|
|
668
|
+
this.rehomeFrame(frame, actor);
|
|
669
|
+
task.managedLease = this.rehomeLease(task.managedLease, actor.budget);
|
|
670
|
+
}
|
|
671
|
+
rehomeFrame(frame, actor) {
|
|
672
|
+
if (frame.accountActor === actor)
|
|
673
|
+
return;
|
|
674
|
+
const previous = frame.accountActor;
|
|
675
|
+
frame.accountActor = actor;
|
|
676
|
+
frame.rcOwner = actor.rcOwner;
|
|
677
|
+
frame.context.actor = actor;
|
|
678
|
+
frame.context.id = actor.id;
|
|
679
|
+
const cells = new Set();
|
|
680
|
+
const rehome = (cell) => {
|
|
681
|
+
if (cells.has(cell))
|
|
682
|
+
return;
|
|
683
|
+
cells.add(cell);
|
|
684
|
+
cell.budget = actor.budget;
|
|
685
|
+
if (cell.valid && cell.type != null)
|
|
686
|
+
this.stampManaged(cell.value, cell.type, actor);
|
|
687
|
+
cell.lease = this.rehomeLease(cell.lease, actor.budget);
|
|
688
|
+
if (!cell.lease && cell.valid && cell.type != null)
|
|
689
|
+
cell.lease = actor.budget?.retain({
|
|
690
|
+
value: cell.value,
|
|
691
|
+
type: cell.type,
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
const ref = (value) => {
|
|
695
|
+
if (value.kind === "cell")
|
|
696
|
+
rehome(value.cell);
|
|
697
|
+
else
|
|
698
|
+
ref(value.parent);
|
|
699
|
+
};
|
|
700
|
+
for (const value of frame.env.values())
|
|
701
|
+
ref(value);
|
|
702
|
+
for (const cell of frame.places.values())
|
|
703
|
+
rehome(cell);
|
|
704
|
+
frame.managedInputs = frame.managedInputs.flatMap((lease) => {
|
|
705
|
+
const replacement = this.rehomeLease(lease, actor.budget);
|
|
706
|
+
return replacement ? [replacement] : [];
|
|
707
|
+
});
|
|
708
|
+
for (const cleanup of frame.managedCleanup)
|
|
709
|
+
cleanup.lease = this.rehomeLease(cleanup.lease, actor.budget);
|
|
710
|
+
for (const child of frame.managedChildren)
|
|
711
|
+
this.rehomeFrame(child.context.activeFrame ?? child, actor);
|
|
712
|
+
for (const scope of frame.scopes.values())
|
|
713
|
+
for (const task of scope.tasks)
|
|
714
|
+
this.rehomeTask(task, actor);
|
|
715
|
+
if (frame.caller)
|
|
716
|
+
this.rehomeFrame(frame.caller, actor);
|
|
717
|
+
previous?.budget?.changed();
|
|
718
|
+
actor.budget?.changed();
|
|
719
|
+
}
|
|
720
|
+
failActorBudget(actor) {
|
|
721
|
+
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
722
|
+
if (!actor.active)
|
|
723
|
+
this.crashActor(actor, fault);
|
|
724
|
+
else {
|
|
725
|
+
// A suspended turn owns its frame until authored unwind and lexical
|
|
726
|
+
// task drains finish. Admission must not retire its state underneath it.
|
|
727
|
+
actor.closing = true;
|
|
728
|
+
actor.crashing ??= fault;
|
|
729
|
+
actor.active.pendingCancellation ??= fault;
|
|
730
|
+
actor.active.cancel?.(fault);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
managedRetained(value, type) {
|
|
734
|
+
const row = this.managed.row(type);
|
|
735
|
+
const typed = (value) => ({
|
|
736
|
+
value,
|
|
737
|
+
type: this.managed.type(value),
|
|
738
|
+
});
|
|
739
|
+
if (row.kind === "generator") {
|
|
740
|
+
const generator = this.generatorFor(value);
|
|
741
|
+
return {
|
|
742
|
+
identity: generator,
|
|
743
|
+
bytes: BigInt(row.payload_bytes),
|
|
744
|
+
children: [
|
|
745
|
+
typed(generator.callable),
|
|
746
|
+
...this.frameManagedRoots(generator.frame),
|
|
747
|
+
],
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
if (row.kind === "actor_call") {
|
|
751
|
+
const task = this.taskFor(value);
|
|
752
|
+
const reply = !task.taken && !task.fault && task.managedReply;
|
|
753
|
+
return {
|
|
754
|
+
identity: task,
|
|
755
|
+
bytes: reply ? this.managed.width(row.reply) : 0n,
|
|
756
|
+
children: reply ? [{ value: reply, type: row.reply }] : [],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (row.kind !== "provider")
|
|
760
|
+
throw new Error("managed retained visitor received no provider");
|
|
761
|
+
switch (row.provider) {
|
|
762
|
+
case "json":
|
|
763
|
+
case "yaml":
|
|
764
|
+
case "toml": {
|
|
765
|
+
if (value.kind !== "encoding")
|
|
766
|
+
throw new Error("managed encoding has no tree");
|
|
767
|
+
const retained = encodingStorage(value);
|
|
768
|
+
return {
|
|
769
|
+
identity: retained,
|
|
770
|
+
bytes: value.value === null ? 0n : retained.bytes,
|
|
771
|
+
children: [],
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
case "task": {
|
|
775
|
+
const task = this.taskFor(value);
|
|
776
|
+
const reply = task.resultInitialized &&
|
|
777
|
+
!task.taken &&
|
|
778
|
+
!task.fault &&
|
|
779
|
+
(!task.actorError || task.actorError === "Failed");
|
|
780
|
+
const children = task.frame && !task.done
|
|
781
|
+
? this.frameManagedRoots(task.frame.context.activeFrame ?? task.frame)
|
|
782
|
+
: [];
|
|
783
|
+
if (task.callable?.kind === "closure" && !task.done)
|
|
784
|
+
children.push(typed(task.callable));
|
|
785
|
+
if (reply)
|
|
786
|
+
children.push({
|
|
787
|
+
value: task.managedResult ?? task.value,
|
|
788
|
+
type: row.children[0],
|
|
789
|
+
});
|
|
790
|
+
return {
|
|
791
|
+
identity: task,
|
|
792
|
+
bytes: (task.invocationPending
|
|
793
|
+
? BigInt(row.invocation_slot_bytes ??
|
|
794
|
+
(() => {
|
|
795
|
+
throw new Error("task invocation has no canonical carrier width");
|
|
796
|
+
})())
|
|
797
|
+
: 0n) +
|
|
798
|
+
this.managed.width(row.children[0]) +
|
|
799
|
+
(task.memberBytes ?? 0n),
|
|
800
|
+
children,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
case "sink": {
|
|
804
|
+
if (value.kind !== "sink")
|
|
805
|
+
throw new Error("typed sink has no endpoint");
|
|
806
|
+
const sender = this.senderEndpoints.get(value.channelId);
|
|
807
|
+
if (sender) {
|
|
808
|
+
const children = [
|
|
809
|
+
typed(sender.owner.sink),
|
|
810
|
+
typed(sender.owner.encoder),
|
|
811
|
+
];
|
|
812
|
+
return {
|
|
813
|
+
identity: sender.owner,
|
|
814
|
+
bytes: children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n),
|
|
815
|
+
children,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
identity: this.pipes.managedRetained(value).identity,
|
|
820
|
+
bytes: 0n,
|
|
821
|
+
children: [],
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
case "stream": {
|
|
825
|
+
if (value.kind !== "stream")
|
|
826
|
+
throw new Error("typed stream has no endpoint");
|
|
827
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
828
|
+
if (adapter) {
|
|
829
|
+
const generatorType = adapter.generator.managedType;
|
|
830
|
+
if (generatorType == null)
|
|
831
|
+
throw new Error("generator stream lacks its producer descriptor");
|
|
832
|
+
const children = [
|
|
833
|
+
{
|
|
834
|
+
value: { kind: "generator", id: adapter.generator.id },
|
|
835
|
+
type: generatorType,
|
|
836
|
+
},
|
|
837
|
+
];
|
|
838
|
+
if (adapter.pending)
|
|
839
|
+
children.push({
|
|
840
|
+
value: adapter.pending,
|
|
841
|
+
type: row.children[0],
|
|
842
|
+
slots: true,
|
|
843
|
+
identity: adapter,
|
|
844
|
+
});
|
|
845
|
+
return {
|
|
846
|
+
identity: adapter,
|
|
847
|
+
bytes: this.managed.width(generatorType),
|
|
848
|
+
children,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
const graph = this.pipes.managedRetained(value);
|
|
852
|
+
return {
|
|
853
|
+
identity: graph.identity,
|
|
854
|
+
bytes: 0n,
|
|
855
|
+
children: graph.queued.map((item) => ({
|
|
856
|
+
value: item.value,
|
|
857
|
+
type: row.children[0],
|
|
858
|
+
slots: true,
|
|
859
|
+
identity: item,
|
|
860
|
+
})),
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
default:
|
|
864
|
+
throw new HeapExceeded();
|
|
865
|
+
}
|
|
428
866
|
}
|
|
429
867
|
invalidate(act, id) {
|
|
430
868
|
// A boundary's transfer of a trivial value has no ownership to end. SIR
|
|
@@ -476,11 +914,26 @@ class ExecutorV1 {
|
|
|
476
914
|
/// and a borrow passes the live value.
|
|
477
915
|
boundary(act, operand) {
|
|
478
916
|
const value = this.read(act, operand.value);
|
|
917
|
+
const budget = act.context.actor?.budget;
|
|
918
|
+
const type = act.managedTypes.get(operand.value);
|
|
919
|
+
const transferable = act.context.generator || act.context.retainsTransferableFrame;
|
|
920
|
+
if (!budget && transferable && type != null)
|
|
921
|
+
this.managed.stampGraph({ value, type });
|
|
479
922
|
switch (operand.decision) {
|
|
480
923
|
case "copy":
|
|
481
|
-
case "snapshot":
|
|
482
|
-
|
|
924
|
+
case "snapshot": {
|
|
925
|
+
if (budget && type != null)
|
|
926
|
+
budget.check([{ value: this.managed.copy(value, type), type }]);
|
|
927
|
+
const copy = cloneValue(value);
|
|
928
|
+
if (!budget && transferable && type != null)
|
|
929
|
+
this.managed.stampGraph({ value: copy, type });
|
|
930
|
+
if (type != null)
|
|
931
|
+
act.managedInputs.push(this.retainRoots([{ value: copy, type }], budget));
|
|
932
|
+
return copy;
|
|
933
|
+
}
|
|
483
934
|
case "move":
|
|
935
|
+
if (type != null)
|
|
936
|
+
act.managedInputs.push(this.retainRoots([{ value, type }], budget));
|
|
484
937
|
this.invalidate(act, operand.value);
|
|
485
938
|
return value;
|
|
486
939
|
default:
|
|
@@ -489,6 +942,24 @@ class ExecutorV1 {
|
|
|
489
942
|
}
|
|
490
943
|
// ── instructions ─────────────────────────────────────────────────────────
|
|
491
944
|
execute(act, op) {
|
|
945
|
+
try {
|
|
946
|
+
this.executeOperation(act, op);
|
|
947
|
+
}
|
|
948
|
+
finally {
|
|
949
|
+
switch (op.op) {
|
|
950
|
+
case "store.init":
|
|
951
|
+
case "store.assign":
|
|
952
|
+
case "load.take":
|
|
953
|
+
case "destructure":
|
|
954
|
+
case "variant.destructure":
|
|
955
|
+
case "destroy_value":
|
|
956
|
+
case "end_lifetime":
|
|
957
|
+
case "task_scope.close":
|
|
958
|
+
act.context.actor?.budget?.changed();
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
executeOperation(act, op) {
|
|
492
963
|
switch (op.op) {
|
|
493
964
|
case "coverage.hit": {
|
|
494
965
|
const key = String(op.counter);
|
|
@@ -532,8 +1003,10 @@ class ExecutorV1 {
|
|
|
532
1003
|
return;
|
|
533
1004
|
case "destroy_value": {
|
|
534
1005
|
const value = this.read(act, op.value);
|
|
1006
|
+
const type = act.managedTypes.get(op.value);
|
|
1007
|
+
const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
|
|
535
1008
|
this.invalidate(act, op.value);
|
|
536
|
-
this.releaseOperation(act, value);
|
|
1009
|
+
this.releaseOperation(act, value, () => cleanup?.release());
|
|
537
1010
|
return;
|
|
538
1011
|
}
|
|
539
1012
|
case "begin_borrow":
|
|
@@ -543,20 +1016,34 @@ class ExecutorV1 {
|
|
|
543
1016
|
case "finish_linear_receiver":
|
|
544
1017
|
return;
|
|
545
1018
|
case "alloc_place":
|
|
546
|
-
act.places.set(op.place, {
|
|
1019
|
+
act.places.set(op.place, {
|
|
1020
|
+
value: UNIT,
|
|
1021
|
+
valid: false,
|
|
1022
|
+
budget: act.context.actor?.budget,
|
|
1023
|
+
type: act.fn.places[op.place]?.managed_type ?? undefined,
|
|
1024
|
+
});
|
|
547
1025
|
return;
|
|
548
1026
|
case "store.init":
|
|
549
1027
|
writeRef(this.placeRef(act, op.place), this.read(act, op.value));
|
|
1028
|
+
this.invalidate(act, op.value);
|
|
550
1029
|
return;
|
|
551
1030
|
case "store.assign": {
|
|
552
1031
|
const ref = this.placeRef(act, op.place);
|
|
553
1032
|
const replacement = this.read(act, op.value);
|
|
554
1033
|
const previous = liveRef(ref) ? readRef(ref) : UNIT;
|
|
1034
|
+
const type = act.fn.places[op.place]?.managed_type;
|
|
1035
|
+
const cleanup = type == null
|
|
1036
|
+
? undefined
|
|
1037
|
+
: this.retainCleanup(act, [{ value: previous, type }]);
|
|
555
1038
|
// Retire the old owner before invoking user close code. Publish the
|
|
556
1039
|
// replacement after cleanup, including when close records a fault;
|
|
557
1040
|
// the following cleanup.dispatch then drains the new owner once.
|
|
558
1041
|
invalidateRef(ref);
|
|
559
|
-
this.releaseOperation(act, previous, () =>
|
|
1042
|
+
this.releaseOperation(act, previous, () => {
|
|
1043
|
+
writeRef(ref, replacement);
|
|
1044
|
+
this.invalidate(act, op.value);
|
|
1045
|
+
cleanup?.release();
|
|
1046
|
+
});
|
|
560
1047
|
return;
|
|
561
1048
|
}
|
|
562
1049
|
case "load.copy":
|
|
@@ -574,8 +1061,10 @@ class ExecutorV1 {
|
|
|
574
1061
|
case "end_lifetime": {
|
|
575
1062
|
const ref = this.placeRef(act, op.place);
|
|
576
1063
|
const value = liveRef(ref) ? readRef(ref) : UNIT;
|
|
1064
|
+
const type = act.fn.places[op.place]?.managed_type;
|
|
1065
|
+
const cleanup = type == null ? undefined : this.retainCleanup(act, [{ value, type }]);
|
|
577
1066
|
invalidateRef(ref);
|
|
578
|
-
this.releaseOperation(act, value);
|
|
1067
|
+
this.releaseOperation(act, value, () => cleanup?.release());
|
|
579
1068
|
return;
|
|
580
1069
|
}
|
|
581
1070
|
case "tuple.make":
|
|
@@ -584,6 +1073,7 @@ class ExecutorV1 {
|
|
|
584
1073
|
typeId: "",
|
|
585
1074
|
fields: op.elements.map((id) => this.read(act, id)),
|
|
586
1075
|
});
|
|
1076
|
+
op.elements.forEach((id) => this.invalidate(act, id));
|
|
587
1077
|
return;
|
|
588
1078
|
case "tuple.get":
|
|
589
1079
|
this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.tuple))[op.index] ?? UNIT));
|
|
@@ -595,6 +1085,7 @@ class ExecutorV1 {
|
|
|
595
1085
|
shape: op.shape ?? undefined,
|
|
596
1086
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
597
1087
|
});
|
|
1088
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
598
1089
|
return;
|
|
599
1090
|
case "aggregate.project_copy":
|
|
600
1091
|
this.define(act, op.dst, cloneValue(fieldsOf(this.read(act, op.aggregate))[op.field] ?? UNIT));
|
|
@@ -616,14 +1107,19 @@ class ExecutorV1 {
|
|
|
616
1107
|
elementType: "",
|
|
617
1108
|
items: op.elements.map((id) => this.read(act, id)),
|
|
618
1109
|
});
|
|
1110
|
+
op.elements.forEach((id) => this.invalidate(act, id));
|
|
619
1111
|
return;
|
|
620
|
-
case "array.repeat":
|
|
1112
|
+
case "array.repeat": {
|
|
1113
|
+
const seed = this.read(act, op.value);
|
|
1114
|
+
const count = this.arrayRepeatLength(op);
|
|
621
1115
|
this.define(act, op.dst, {
|
|
622
1116
|
kind: "vector",
|
|
623
1117
|
elementType: "",
|
|
624
|
-
items: Array.from({ length:
|
|
1118
|
+
items: Array.from({ length: count }, (_, index) => index === 0 ? seed : cloneValue(seed)),
|
|
625
1119
|
});
|
|
1120
|
+
this.invalidate(act, op.value);
|
|
626
1121
|
return;
|
|
1122
|
+
}
|
|
627
1123
|
case "variant.make":
|
|
628
1124
|
this.define(act, op.dst, {
|
|
629
1125
|
kind: "enum",
|
|
@@ -632,6 +1128,7 @@ class ExecutorV1 {
|
|
|
632
1128
|
tag: op.variant,
|
|
633
1129
|
payload: op.fields.map((id) => this.read(act, id)),
|
|
634
1130
|
});
|
|
1131
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
635
1132
|
return;
|
|
636
1133
|
case "variant.is":
|
|
637
1134
|
this.define(act, op.dst, {
|
|
@@ -691,6 +1188,7 @@ class ExecutorV1 {
|
|
|
691
1188
|
fields: op.fields.map((id) => this.read(act, id)),
|
|
692
1189
|
},
|
|
693
1190
|
});
|
|
1191
|
+
op.fields.forEach((id) => this.invalidate(act, id));
|
|
694
1192
|
return;
|
|
695
1193
|
}
|
|
696
1194
|
case "callable.coerce": {
|
|
@@ -716,6 +1214,7 @@ class ExecutorV1 {
|
|
|
716
1214
|
vtable: op.vtable,
|
|
717
1215
|
value: this.read(act, op.value),
|
|
718
1216
|
});
|
|
1217
|
+
this.invalidate(act, op.value);
|
|
719
1218
|
return;
|
|
720
1219
|
// The registration reserves the free places the body reaches; the drain
|
|
721
1220
|
// order is already the CFG's, so there is nothing to record here.
|
|
@@ -771,9 +1270,12 @@ class ExecutorV1 {
|
|
|
771
1270
|
id: task.id,
|
|
772
1271
|
})),
|
|
773
1272
|
}, () => {
|
|
774
|
-
for (const task of group?.tasks ?? [])
|
|
775
|
-
this.tasks.delete(task.id);
|
|
776
1273
|
act.scopes.delete(op.scope);
|
|
1274
|
+
act.context.actor?.budget?.changed();
|
|
1275
|
+
for (const task of group?.tasks ?? [])
|
|
1276
|
+
task.scopeAlive = false;
|
|
1277
|
+
for (const task of group?.tasks ?? [])
|
|
1278
|
+
this.releaseTaskState(task);
|
|
777
1279
|
});
|
|
778
1280
|
return;
|
|
779
1281
|
}
|
|
@@ -786,7 +1288,10 @@ class ExecutorV1 {
|
|
|
786
1288
|
actor: act.context.actor,
|
|
787
1289
|
rcOwner: act.rcOwner,
|
|
788
1290
|
requestDeadline: this.requestDeadline(act),
|
|
789
|
-
returned: (value) => this.closeValueAsync(value, null, (fault) =>
|
|
1291
|
+
returned: (value, released) => this.closeValueAsync(value, null, (fault) => {
|
|
1292
|
+
released();
|
|
1293
|
+
this.finishGenerator(generator, fault);
|
|
1294
|
+
}, act.context.actor, act.rcOwner),
|
|
790
1295
|
failed: (fault) => this.finishGenerator(generator, fault),
|
|
791
1296
|
};
|
|
792
1297
|
const generator = {
|
|
@@ -807,8 +1312,9 @@ class ExecutorV1 {
|
|
|
807
1312
|
const scope = act.scopes.get(op.scope);
|
|
808
1313
|
if (!scope)
|
|
809
1314
|
throw new Error("race has no task scope");
|
|
810
|
-
const
|
|
811
|
-
|
|
1315
|
+
const memberValues = op.members.map((operand) => this.read(act, operand));
|
|
1316
|
+
const members = op.members.map((operand, index) => {
|
|
1317
|
+
const member = this.taskFor(memberValues[index]);
|
|
812
1318
|
this.invalidate(act, operand);
|
|
813
1319
|
if (member.grouped)
|
|
814
1320
|
throw new Error("race reuses a task handle");
|
|
@@ -817,7 +1323,8 @@ class ExecutorV1 {
|
|
|
817
1323
|
});
|
|
818
1324
|
if (!members.length)
|
|
819
1325
|
throw new Error("race requires members");
|
|
820
|
-
const task = this.newTask();
|
|
1326
|
+
const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), BigInt(op.member_slot_bytes) * BigInt(members.length), true);
|
|
1327
|
+
task.members = memberValues;
|
|
821
1328
|
scope.tasks.push(task);
|
|
822
1329
|
let selected = false;
|
|
823
1330
|
let winner = null;
|
|
@@ -839,6 +1346,9 @@ class ExecutorV1 {
|
|
|
839
1346
|
winner = first;
|
|
840
1347
|
value = first.value;
|
|
841
1348
|
first.value = UNIT;
|
|
1349
|
+
first.taken = true;
|
|
1350
|
+
task.value = value;
|
|
1351
|
+
task.resultInitialized = true;
|
|
842
1352
|
}
|
|
843
1353
|
}
|
|
844
1354
|
if (!cancelled) {
|
|
@@ -869,13 +1379,18 @@ class ExecutorV1 {
|
|
|
869
1379
|
.filter((member) => member !== winner)
|
|
870
1380
|
.map((member) => {
|
|
871
1381
|
const result = member.value;
|
|
872
|
-
member.value = UNIT;
|
|
873
1382
|
return result;
|
|
874
1383
|
});
|
|
875
1384
|
this.closeValueAsync({ kind: "vector", elementType: "race result", items: losers }, this.faultText(failure), (closeFault) => {
|
|
1385
|
+
for (const member of members)
|
|
1386
|
+
if (member !== winner) {
|
|
1387
|
+
member.value = UNIT;
|
|
1388
|
+
member.taken = true;
|
|
1389
|
+
}
|
|
876
1390
|
failure = combineFaults(failure, closeFault);
|
|
877
1391
|
const finish = (releaseFault = null) => {
|
|
878
1392
|
failure = combineFaults(failure, releaseFault);
|
|
1393
|
+
this.releaseRaceMembers(task);
|
|
879
1394
|
this.settleTask(task, failure ? UNIT : value, failure);
|
|
880
1395
|
for (const changed of [...scope.changed])
|
|
881
1396
|
changed();
|
|
@@ -941,8 +1456,9 @@ class ExecutorV1 {
|
|
|
941
1456
|
const specialization = op.op === "task.invoke" && "specialization" in op.callee
|
|
942
1457
|
? op.callee.specialization
|
|
943
1458
|
: null;
|
|
944
|
-
const task = this.newTask();
|
|
1459
|
+
const task = this.newTask(act.context.actor?.budget, act.managedTypes.get(op.dst), 0n, true);
|
|
945
1460
|
group.tasks.push(task);
|
|
1461
|
+
let releaseReturn = () => { };
|
|
946
1462
|
const settle = (value, fault) => {
|
|
947
1463
|
if (task.done || task.finishing)
|
|
948
1464
|
return;
|
|
@@ -955,6 +1471,7 @@ class ExecutorV1 {
|
|
|
955
1471
|
if (!fault || (fault.kind === "panic" && fault.cancelled))
|
|
956
1472
|
fault = closeFault ?? fault;
|
|
957
1473
|
this.settleTask(task, value, fault);
|
|
1474
|
+
releaseReturn();
|
|
958
1475
|
if (!task.grouped &&
|
|
959
1476
|
fault &&
|
|
960
1477
|
!(fault.kind === "panic" && fault.cancelled)) {
|
|
@@ -1008,9 +1525,11 @@ class ExecutorV1 {
|
|
|
1008
1525
|
const context = {
|
|
1009
1526
|
id: task.id,
|
|
1010
1527
|
actor: act.context.actor,
|
|
1528
|
+
retainsTransferableFrame: !!act.context.generator || act.context.retainsTransferableFrame,
|
|
1011
1529
|
rcOwner: act.rcOwner,
|
|
1012
1530
|
requestDeadline: this.requestDeadline(act),
|
|
1013
|
-
returned: (value) => {
|
|
1531
|
+
returned: (value, released) => {
|
|
1532
|
+
releaseReturn = released;
|
|
1014
1533
|
if (dynamicEntry?.receiver_handback &&
|
|
1015
1534
|
dynamicReceiver?.kind === "dyn") {
|
|
1016
1535
|
if (value.kind !== "record" || value.fields.length !== 2)
|
|
@@ -1039,6 +1558,9 @@ class ExecutorV1 {
|
|
|
1039
1558
|
},
|
|
1040
1559
|
};
|
|
1041
1560
|
const frame = this.activate(this.functionAt(calleeFunction(callable, specialization)), callable.kind === "closure" ? [callable.environment, ...args] : args, null, null, undefined, null, context);
|
|
1561
|
+
task.frame = frame;
|
|
1562
|
+
task.callable = callable;
|
|
1563
|
+
task.invocationPending = true;
|
|
1042
1564
|
task.cancel = () => {
|
|
1043
1565
|
if (task.done || task.finishing)
|
|
1044
1566
|
return;
|
|
@@ -1052,6 +1574,8 @@ class ExecutorV1 {
|
|
|
1052
1574
|
this.scheduler.enqueue(task.id, () => {
|
|
1053
1575
|
if (task.done || task.finishing)
|
|
1054
1576
|
return;
|
|
1577
|
+
task.invocationPending = false;
|
|
1578
|
+
task.budget?.changed();
|
|
1055
1579
|
this.runFrame(frame);
|
|
1056
1580
|
});
|
|
1057
1581
|
this.trace.allocateId("task", task.id, act.context.id);
|
|
@@ -1091,7 +1615,43 @@ class ExecutorV1 {
|
|
|
1091
1615
|
}
|
|
1092
1616
|
// ── terminators ──────────────────────────────────────────────────────────
|
|
1093
1617
|
terminate(act, term) {
|
|
1618
|
+
const mutable = [
|
|
1619
|
+
"runtime.call",
|
|
1620
|
+
"extern.call",
|
|
1621
|
+
"value.call",
|
|
1622
|
+
"wire.codec",
|
|
1623
|
+
"suspend",
|
|
1624
|
+
].includes(term.op);
|
|
1625
|
+
if (mutable)
|
|
1626
|
+
act.context.actor?.budget?.changed();
|
|
1627
|
+
try {
|
|
1628
|
+
this.terminateOperation(act, term);
|
|
1629
|
+
}
|
|
1630
|
+
finally {
|
|
1631
|
+
if (mutable)
|
|
1632
|
+
act.context.actor?.budget?.changed();
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
terminateOperation(act, term) {
|
|
1094
1636
|
switch (term.op) {
|
|
1637
|
+
case "materialize": {
|
|
1638
|
+
const budget = act.context.actor?.budget;
|
|
1639
|
+
try {
|
|
1640
|
+
if (term.operation.op === "array.repeat")
|
|
1641
|
+
this.arrayRepeatLength(term.operation);
|
|
1642
|
+
if (budget)
|
|
1643
|
+
this.prepareMaterialize(act, term.operation, budget);
|
|
1644
|
+
}
|
|
1645
|
+
catch (error) {
|
|
1646
|
+
if (!(error instanceof HeapExceeded))
|
|
1647
|
+
throw error;
|
|
1648
|
+
this.takeEdge(act, term.failure);
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
this.execute(act, term.operation);
|
|
1652
|
+
this.takeEdge(act, term.normal);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1095
1655
|
case "goto":
|
|
1096
1656
|
this.takeEdge(act, term.edge);
|
|
1097
1657
|
return;
|
|
@@ -1107,6 +1667,11 @@ class ExecutorV1 {
|
|
|
1107
1667
|
throw new Error(`switch.variant has no arm for tag ${tag}`);
|
|
1108
1668
|
}
|
|
1109
1669
|
this.spread(act, payloadOf(scrutinee), arm.fields);
|
|
1670
|
+
if (act.owned.has(term.scrutinee)) {
|
|
1671
|
+
this.invalidate(act, term.scrutinee);
|
|
1672
|
+
this.managed.retire(scrutinee);
|
|
1673
|
+
act.context.actor?.budget?.changed();
|
|
1674
|
+
}
|
|
1110
1675
|
this.takeEdge(act, arm.edge);
|
|
1111
1676
|
return;
|
|
1112
1677
|
}
|
|
@@ -1247,12 +1812,30 @@ class ExecutorV1 {
|
|
|
1247
1812
|
return;
|
|
1248
1813
|
}
|
|
1249
1814
|
case "collection": {
|
|
1815
|
+
const receiverType = term.args[0] && act.managedTypes.get(term.args[0].value);
|
|
1816
|
+
const receiverLayout = receiverType == null ? undefined : this.managed.row(receiverType);
|
|
1817
|
+
const elementType = receiverLayout &&
|
|
1818
|
+
(receiverLayout.kind === "vector" ||
|
|
1819
|
+
receiverLayout.kind === "array")
|
|
1820
|
+
? receiverLayout.element
|
|
1821
|
+
: undefined;
|
|
1250
1822
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1251
|
-
const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape);
|
|
1823
|
+
const iterator = collectionOperation(this.pkg, registration, args, term.callbacks ?? [], term.result_member_shapes?.[1] ?? term.result_shape, elementType);
|
|
1252
1824
|
this.runValueProgram(act, term, args, iterator);
|
|
1253
1825
|
return;
|
|
1254
1826
|
}
|
|
1255
1827
|
case "shared": {
|
|
1828
|
+
if (act.context.actor?.budget &&
|
|
1829
|
+
(registration.operation === "RcNew" ||
|
|
1830
|
+
registration.operation === "RcSet")) {
|
|
1831
|
+
const constructing = registration.operation === "RcNew";
|
|
1832
|
+
const operand = term.args[constructing ? 0 : 1];
|
|
1833
|
+
const type = act.managedTypes.get(operand.value);
|
|
1834
|
+
if (type == null)
|
|
1835
|
+
throw new Error("Rc payload lacks its typed descriptor");
|
|
1836
|
+
if (!this.checkRuntimePayload(act, term, constructing ? this.managed.width(type) : 0n, [{ value: this.read(act, operand.value), type }]))
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1256
1839
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1257
1840
|
this.runValueProgram(act, term, args, sharedOperation(registration.operation, args, act.rcOwner, term.shared_payload_type ?? "", (value) => this.variant(term.result_shape, value ? "OptionSome" : "OptionNone", value ? [value] : [])));
|
|
1258
1841
|
return;
|
|
@@ -1425,11 +2008,150 @@ class ExecutorV1 {
|
|
|
1425
2008
|
}
|
|
1426
2009
|
}
|
|
1427
2010
|
}
|
|
2011
|
+
/** Reserve the exact constructor/copy graph before any operand is consumed. */
|
|
2012
|
+
arrayRepeatLength(op) {
|
|
2013
|
+
const type = op.definitions?.[0]?.managed_type;
|
|
2014
|
+
const row = type == null ? undefined : this.managed.row(type);
|
|
2015
|
+
if (row?.kind !== "array")
|
|
2016
|
+
throw new Error("array repeat lacks its canonical length");
|
|
2017
|
+
const count = BigInt(row.length);
|
|
2018
|
+
this.managed.width(row.element, count);
|
|
2019
|
+
if (count > 0xffffffffn)
|
|
2020
|
+
throw new HeapExceeded();
|
|
2021
|
+
return Number(count);
|
|
2022
|
+
}
|
|
2023
|
+
prepareMaterialize(act, op, budget) {
|
|
2024
|
+
const type = op.definitions?.[0]?.managed_type;
|
|
2025
|
+
if (type == null)
|
|
2026
|
+
throw new Error("materialization has no managed result descriptor");
|
|
2027
|
+
const read = (id) => this.read(act, id);
|
|
2028
|
+
const copy = (value) => this.managed.copy(value, type);
|
|
2029
|
+
let value;
|
|
2030
|
+
switch (op.op) {
|
|
2031
|
+
case "const.str":
|
|
2032
|
+
value = stringValue(this.pkg.strings[op.str] ?? "");
|
|
2033
|
+
break;
|
|
2034
|
+
case "const.bytes":
|
|
2035
|
+
value = bytesValue(this.pkg.bytes[op.bytes] ?? []);
|
|
2036
|
+
break;
|
|
2037
|
+
case "copy_value":
|
|
2038
|
+
value = copy(read(op.source));
|
|
2039
|
+
break;
|
|
2040
|
+
case "load.copy":
|
|
2041
|
+
value = copy(readRef(this.placeRef(act, op.place)));
|
|
2042
|
+
break;
|
|
2043
|
+
case "tuple.get":
|
|
2044
|
+
value = copy(fieldsOf(read(op.tuple))[op.index]);
|
|
2045
|
+
break;
|
|
2046
|
+
case "aggregate.project_copy":
|
|
2047
|
+
value = copy(fieldsOf(read(op.aggregate))[op.field]);
|
|
2048
|
+
break;
|
|
2049
|
+
case "variant.project_copy":
|
|
2050
|
+
value = copy(payloadOf(read(op.source))[op.field]);
|
|
2051
|
+
break;
|
|
2052
|
+
case "array.make":
|
|
2053
|
+
value = {
|
|
2054
|
+
kind: "vector",
|
|
2055
|
+
elementType: "",
|
|
2056
|
+
items: op.elements.map(read),
|
|
2057
|
+
};
|
|
2058
|
+
break;
|
|
2059
|
+
case "array.repeat": {
|
|
2060
|
+
const count = this.arrayRepeatLength(op);
|
|
2061
|
+
const row = this.managed.row(type);
|
|
2062
|
+
if (row.kind !== "array")
|
|
2063
|
+
throw new Error("array repeat lacks its element descriptor");
|
|
2064
|
+
const empty = [];
|
|
2065
|
+
const storage = {
|
|
2066
|
+
items: empty,
|
|
2067
|
+
capacity: count,
|
|
2068
|
+
owners: 1,
|
|
2069
|
+
bytes: false,
|
|
2070
|
+
shareOnCopy: row.share_on_copy,
|
|
2071
|
+
};
|
|
2072
|
+
value = { kind: "vector", elementType: "", items: empty, storage };
|
|
2073
|
+
budget.check([{ value, type }]);
|
|
2074
|
+
const seed = read(op.value);
|
|
2075
|
+
storage.items = Array.from({ length: count }, (_, index) => index ? this.managed.copy(seed, row.element) : seed);
|
|
2076
|
+
value.items = storage.items;
|
|
2077
|
+
break;
|
|
2078
|
+
}
|
|
2079
|
+
case "variant.make":
|
|
2080
|
+
value = {
|
|
2081
|
+
kind: "enum",
|
|
2082
|
+
typeId: "",
|
|
2083
|
+
shape: op.shape,
|
|
2084
|
+
tag: op.variant,
|
|
2085
|
+
payload: op.fields.map(read),
|
|
2086
|
+
};
|
|
2087
|
+
break;
|
|
2088
|
+
case "closure.make":
|
|
2089
|
+
value = {
|
|
2090
|
+
kind: "closure",
|
|
2091
|
+
entries: this.closureAt(op.closure).entries,
|
|
2092
|
+
specialization: null,
|
|
2093
|
+
environment: {
|
|
2094
|
+
kind: "record",
|
|
2095
|
+
typeId: "",
|
|
2096
|
+
fields: op.fields.map(read),
|
|
2097
|
+
},
|
|
2098
|
+
};
|
|
2099
|
+
break;
|
|
2100
|
+
case "dyn.make":
|
|
2101
|
+
value = { kind: "dyn", vtable: op.vtable, value: read(op.value) };
|
|
2102
|
+
break;
|
|
2103
|
+
case "generator.make": {
|
|
2104
|
+
const row = this.managed.row(type);
|
|
2105
|
+
if (row.kind !== "generator")
|
|
2106
|
+
throw new Error("generator lacks its payload descriptor");
|
|
2107
|
+
const callable = read(op.callable);
|
|
2108
|
+
budget.checkPayload(BigInt(row.payload_bytes), [
|
|
2109
|
+
{ value: callable, type: this.managed.type(callable) },
|
|
2110
|
+
]);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
case "task.spawn":
|
|
2114
|
+
case "task.invoke":
|
|
2115
|
+
case "task.race": {
|
|
2116
|
+
const row = this.managed.row(type);
|
|
2117
|
+
if (row.kind !== "provider" || row.provider !== "task")
|
|
2118
|
+
throw new Error("checked task lacks its canonical provider descriptor");
|
|
2119
|
+
const carrier = op.op === "task.race"
|
|
2120
|
+
? BigInt(op.member_slot_bytes) * BigInt(op.members.length)
|
|
2121
|
+
: BigInt(row.invocation_slot_bytes);
|
|
2122
|
+
const children = [];
|
|
2123
|
+
if (op.op === "task.invoke") {
|
|
2124
|
+
const operands = [...op.args];
|
|
2125
|
+
if ("receiver" in op.callee)
|
|
2126
|
+
operands.push(op.callee.receiver);
|
|
2127
|
+
for (const operand of operands) {
|
|
2128
|
+
const type = act.managedTypes.get(operand.value);
|
|
2129
|
+
if (type == null)
|
|
2130
|
+
throw new Error("task input lacks its canonical descriptor");
|
|
2131
|
+
const value = read(operand.value);
|
|
2132
|
+
children.push({
|
|
2133
|
+
value: operand.decision === "copy" || operand.decision === "snapshot"
|
|
2134
|
+
? this.managed.copy(value, type)
|
|
2135
|
+
: value,
|
|
2136
|
+
type,
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
budget.checkPayload(this.managed.width(row.children[0]) + carrier, children);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
default:
|
|
2144
|
+
throw new Error(`unexpected managed materialization ${op.op}`);
|
|
2145
|
+
}
|
|
2146
|
+
budget.check([{ value, type }]);
|
|
2147
|
+
}
|
|
1428
2148
|
takeEdge(act, edge) {
|
|
1429
2149
|
// Read every argument before rebinding, so a block that branches to itself
|
|
1430
2150
|
// with permuted parameters binds the incoming values, not the rebound ones.
|
|
1431
2151
|
const args = edge.args.map((id) => this.refOf(act, id));
|
|
1432
2152
|
const target = this.blockAt(act.fn, edge.to);
|
|
2153
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2154
|
+
lease.release();
|
|
1433
2155
|
for (const [at, param] of target.params.entries()) {
|
|
1434
2156
|
act.env.set(param.value, args[at] ?? ownedRef(UNIT));
|
|
1435
2157
|
}
|
|
@@ -1459,9 +2181,53 @@ class ExecutorV1 {
|
|
|
1459
2181
|
throw error;
|
|
1460
2182
|
});
|
|
1461
2183
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
2184
|
+
/** Reserve typed runtime payload before construction or replacement. Refusal
|
|
2185
|
+
* follows the call's existing input ownership and authored cleanup edge. */
|
|
2186
|
+
checkRuntimePayload(act, term, bytes, children) {
|
|
2187
|
+
const budget = act.context.actor?.budget;
|
|
2188
|
+
if (!budget)
|
|
2189
|
+
return true;
|
|
2190
|
+
try {
|
|
2191
|
+
budget.checkPayload(bytes, children);
|
|
2192
|
+
return true;
|
|
2193
|
+
}
|
|
2194
|
+
catch (error) {
|
|
2195
|
+
if (!(error instanceof HeapExceeded))
|
|
2196
|
+
throw error;
|
|
2197
|
+
const fault = { kind: "trap", trap: "heap_exceeded" };
|
|
2198
|
+
if (this.keepsInputs(term)) {
|
|
2199
|
+
// No new payload exists and none of the consuming inputs has moved.
|
|
2200
|
+
this.raiseFault(act, fault, term.unwind);
|
|
2201
|
+
return false;
|
|
2202
|
+
}
|
|
2203
|
+
const consumed = term.args
|
|
2204
|
+
.filter((operand) => operand.decision === "move")
|
|
2205
|
+
.map((operand) => this.boundary(act, operand));
|
|
2206
|
+
this.running = false;
|
|
2207
|
+
this.closeValueAsync({ kind: "record", typeId: "", fields: consumed }, error.message, (cleanupFault) => {
|
|
2208
|
+
this.raiseFault(act, combineFaults(fault, cleanupFault), term.unwind);
|
|
2209
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2210
|
+
}, act.context.actor, act.rcOwner, act);
|
|
2211
|
+
return false;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
/// Run a runtime or extern shim through its authored fault continuation.
|
|
1464
2215
|
callShim(act, term, shim) {
|
|
2216
|
+
const family = term.op === "runtime.call"
|
|
2217
|
+
? this.pkg.runtime_families[term.family]?.family
|
|
2218
|
+
: undefined;
|
|
2219
|
+
const budget = act.context.actor?.budget;
|
|
2220
|
+
if (budget &&
|
|
2221
|
+
(family === "SinkFromEncoder" || family === "StreamFromGenerator")) {
|
|
2222
|
+
const children = term.args.map((operand) => {
|
|
2223
|
+
const type = act.managedTypes.get(operand.value);
|
|
2224
|
+
if (type == null)
|
|
2225
|
+
throw new Error("stream adapter input lacks its typed descriptor");
|
|
2226
|
+
return { value: this.read(act, operand.value), type };
|
|
2227
|
+
});
|
|
2228
|
+
if (!this.checkRuntimePayload(act, term, children.reduce((bytes, child) => bytes + this.managed.width(child.type), 0n), children))
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
1465
2231
|
const args = term.args.map((operand) => this.boundary(act, operand));
|
|
1466
2232
|
// A call that hands back its updated receiver names its value's shape as
|
|
1467
2233
|
// the pair's second member.
|
|
@@ -1477,9 +2243,13 @@ class ExecutorV1 {
|
|
|
1477
2243
|
value = shim(this.host, args, shape, aggregate);
|
|
1478
2244
|
}
|
|
1479
2245
|
catch (error) {
|
|
1480
|
-
if (error instanceof ShimFault) {
|
|
2246
|
+
if (error instanceof ShimFault || error instanceof HeapExceeded) {
|
|
1481
2247
|
this.keepInputs(act, term, args);
|
|
1482
|
-
this.raiseFault(act, {
|
|
2248
|
+
this.raiseFault(act, {
|
|
2249
|
+
kind: "trap",
|
|
2250
|
+
trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
|
|
2251
|
+
message: error.message,
|
|
2252
|
+
}, term.unwind);
|
|
1483
2253
|
return;
|
|
1484
2254
|
}
|
|
1485
2255
|
throw error;
|
|
@@ -1523,16 +2293,21 @@ class ExecutorV1 {
|
|
|
1523
2293
|
this.keepInputs(act, term, args);
|
|
1524
2294
|
this.raiseFault(act, fault, term.unwind);
|
|
1525
2295
|
resume();
|
|
1526
|
-
}, act.context.actor, act.rcOwner);
|
|
2296
|
+
}, act.context.actor, act.rcOwner, act);
|
|
1527
2297
|
};
|
|
1528
2298
|
const next = (value = UNIT) => {
|
|
1529
2299
|
let step;
|
|
1530
2300
|
try {
|
|
1531
2301
|
step = iterator.next(value);
|
|
2302
|
+
act.context.actor?.budget?.changed();
|
|
1532
2303
|
}
|
|
1533
2304
|
catch (error) {
|
|
1534
|
-
if (error instanceof ShimFault) {
|
|
1535
|
-
fail({
|
|
2305
|
+
if (error instanceof ShimFault || error instanceof HeapExceeded) {
|
|
2306
|
+
fail({
|
|
2307
|
+
kind: "trap",
|
|
2308
|
+
trap: error instanceof HeapExceeded ? "heap_exceeded" : error.trap,
|
|
2309
|
+
message: error.message,
|
|
2310
|
+
});
|
|
1536
2311
|
return;
|
|
1537
2312
|
}
|
|
1538
2313
|
throw error;
|
|
@@ -1546,7 +2321,16 @@ class ExecutorV1 {
|
|
|
1546
2321
|
}
|
|
1547
2322
|
else if (step.value.kind === "release") {
|
|
1548
2323
|
const request = step.value;
|
|
2324
|
+
const lease = act.context.actor?.budget ||
|
|
2325
|
+
act.context.generator ||
|
|
2326
|
+
act.context.retainsTransferableFrame
|
|
2327
|
+
? this.retainCleanup(act, (request.retained ?? [request.value]).map((value) => ({
|
|
2328
|
+
value,
|
|
2329
|
+
type: request.retainedType ?? this.managed.type(value),
|
|
2330
|
+
})))
|
|
2331
|
+
: undefined;
|
|
1549
2332
|
this.closeValueAsync(request.value, this.pipeFault(act), (fault) => {
|
|
2333
|
+
lease?.release();
|
|
1550
2334
|
if (fault) {
|
|
1551
2335
|
if (term.op !== "runtime.call" || !term.releases_contents) {
|
|
1552
2336
|
fail(fault, request.onFault);
|
|
@@ -1557,11 +2341,11 @@ class ExecutorV1 {
|
|
|
1557
2341
|
act.fault ??= fault;
|
|
1558
2342
|
}
|
|
1559
2343
|
next();
|
|
1560
|
-
}, act.context.actor, act.rcOwner);
|
|
2344
|
+
}, act.context.actor, act.rcOwner, act);
|
|
1561
2345
|
}
|
|
1562
2346
|
else {
|
|
1563
2347
|
const request = step.value;
|
|
1564
|
-
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner);
|
|
2348
|
+
const child = this.invokeFrame(act.context.actor, request.callee, request.args, next, fail, act.rcOwner, false, act);
|
|
1565
2349
|
act.context.cancel = (fault) => child.cancel?.(fault);
|
|
1566
2350
|
}
|
|
1567
2351
|
};
|
|
@@ -1629,7 +2413,17 @@ class ExecutorV1 {
|
|
|
1629
2413
|
throw new Error(`${term.op} with a "never" result cannot return in the sandbox`);
|
|
1630
2414
|
}
|
|
1631
2415
|
if (term.result && term.result !== "never") {
|
|
1632
|
-
|
|
2416
|
+
try {
|
|
2417
|
+
this.define(act, term.result.value, value);
|
|
2418
|
+
}
|
|
2419
|
+
catch (error) {
|
|
2420
|
+
if (!(error instanceof HeapExceeded))
|
|
2421
|
+
throw error;
|
|
2422
|
+
this.closeValueAsync(value, error.message, (fault) => {
|
|
2423
|
+
this.raiseFault(act, combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault), "unwind" in term ? term.unwind : null);
|
|
2424
|
+
}, act.context.actor, act.rcOwner);
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
1633
2427
|
}
|
|
1634
2428
|
this.takeEdge(act, term.normal);
|
|
1635
2429
|
}
|
|
@@ -1675,9 +2469,20 @@ class ExecutorV1 {
|
|
|
1675
2469
|
}
|
|
1676
2470
|
const cell = item.value;
|
|
1677
2471
|
const payload = cell.value;
|
|
2472
|
+
let payloadLease;
|
|
2473
|
+
if (actor?.budget && cell.managedPayload != null)
|
|
2474
|
+
payloadLease = actor.budget.retain({
|
|
2475
|
+
value: payload,
|
|
2476
|
+
type: cell.managedPayload,
|
|
2477
|
+
slots: true,
|
|
2478
|
+
identity: cell,
|
|
2479
|
+
});
|
|
2480
|
+
cell.managedLease?.release();
|
|
2481
|
+
cell.managedLease = undefined;
|
|
1678
2482
|
cell.value = UNIT;
|
|
1679
2483
|
blocks.delete(cell);
|
|
1680
2484
|
this.closeValueAsync(payload, this.faultText(firstFault), (fault) => {
|
|
2485
|
+
payloadLease?.release();
|
|
1681
2486
|
firstFault = combineFaults(firstFault, fault);
|
|
1682
2487
|
release();
|
|
1683
2488
|
}, actor, owner);
|
|
@@ -1724,7 +2529,39 @@ class ExecutorV1 {
|
|
|
1724
2529
|
const caller = act.caller;
|
|
1725
2530
|
if (!caller) {
|
|
1726
2531
|
this.running = false;
|
|
1727
|
-
|
|
2532
|
+
let reply;
|
|
2533
|
+
if (act.context.reply &&
|
|
2534
|
+
act.context.actor?.budget &&
|
|
2535
|
+
act.block.term.op === "return" &&
|
|
2536
|
+
act.block.term.value) {
|
|
2537
|
+
const type = act.managedTypes.get(act.block.term.value.value);
|
|
2538
|
+
if (type == null)
|
|
2539
|
+
throw new Error("actor reply lacks its managed descriptor");
|
|
2540
|
+
try {
|
|
2541
|
+
if (type != null)
|
|
2542
|
+
reply = act.context.actor.budget.retain({
|
|
2543
|
+
value,
|
|
2544
|
+
type,
|
|
2545
|
+
slots: true,
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
catch (error) {
|
|
2549
|
+
if (!(error instanceof HeapExceeded))
|
|
2550
|
+
throw error;
|
|
2551
|
+
this.closeValueAsync(value, error.message, (fault) => {
|
|
2552
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2553
|
+
lease.release();
|
|
2554
|
+
act.context.failed(combineFaults({ kind: "trap", trap: "heap_exceeded" }, fault));
|
|
2555
|
+
}, act.context.actor, act.rcOwner);
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
const inputs = act.managedInputs.splice(0);
|
|
2560
|
+
act.context.returned(value, () => {
|
|
2561
|
+
reply?.release();
|
|
2562
|
+
for (const lease of inputs)
|
|
2563
|
+
lease.release();
|
|
2564
|
+
});
|
|
1728
2565
|
return;
|
|
1729
2566
|
}
|
|
1730
2567
|
if (!act.normal) {
|
|
@@ -1744,6 +2581,8 @@ class ExecutorV1 {
|
|
|
1744
2581
|
}
|
|
1745
2582
|
this.current = caller;
|
|
1746
2583
|
this.takeEdge(caller, act.normal);
|
|
2584
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2585
|
+
lease.release();
|
|
1747
2586
|
}
|
|
1748
2587
|
dynamicEntry(receiver, slot) {
|
|
1749
2588
|
const table = this.pkg.vtables[receiver.vtable] ??
|
|
@@ -1792,6 +2631,8 @@ class ExecutorV1 {
|
|
|
1792
2631
|
if (!caller) {
|
|
1793
2632
|
this.running = false;
|
|
1794
2633
|
act.context.failed(fault, handback);
|
|
2634
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2635
|
+
lease.release();
|
|
1795
2636
|
return;
|
|
1796
2637
|
}
|
|
1797
2638
|
if (!act.unwind) {
|
|
@@ -1811,6 +2652,8 @@ class ExecutorV1 {
|
|
|
1811
2652
|
caller.fault = fault;
|
|
1812
2653
|
this.current = caller;
|
|
1813
2654
|
this.takeEdge(caller, act.unwind);
|
|
2655
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2656
|
+
lease.release();
|
|
1814
2657
|
}
|
|
1815
2658
|
finishConsumedCallable(act, value, done) {
|
|
1816
2659
|
const callable = act.consumedCallable;
|
|
@@ -1850,6 +2693,7 @@ class ExecutorV1 {
|
|
|
1850
2693
|
const generator = act.context.generator;
|
|
1851
2694
|
if (!generator || !generator.receive)
|
|
1852
2695
|
throw new Error("yield has no generator consumer");
|
|
2696
|
+
generator.frame = act;
|
|
1853
2697
|
const value = this.boundary(act, term.inputs[0]);
|
|
1854
2698
|
const wake = this.park(act, term);
|
|
1855
2699
|
generator.resume = () => wake(UNIT);
|
|
@@ -1895,9 +2739,27 @@ class ExecutorV1 {
|
|
|
1895
2739
|
case "StreamSend": {
|
|
1896
2740
|
const sink = this.boundary(act, term.inputs[0]);
|
|
1897
2741
|
const value = this.boundary(act, term.inputs[1]);
|
|
2742
|
+
const type = act.managedTypes.get(term.inputs[1].value);
|
|
2743
|
+
if (type == null)
|
|
2744
|
+
throw new Error("stream write lacks its typed input descriptor");
|
|
2745
|
+
// The native operation owns one aligned input slot until accepted or
|
|
2746
|
+
// terminal cleanup, independently of the receiving queue's slot.
|
|
1898
2747
|
const wake = this.park(act, term);
|
|
2748
|
+
try {
|
|
2749
|
+
act.managedInputs.push(this.retainRoots([{ value, type, slots: true, identity: {} }], act.context.actor?.budget));
|
|
2750
|
+
}
|
|
2751
|
+
catch (error) {
|
|
2752
|
+
if (!(error instanceof HeapExceeded))
|
|
2753
|
+
throw error;
|
|
2754
|
+
const refused = { kind: "trap", trap: "heap_exceeded" };
|
|
2755
|
+
this.closeValueAsync(value, error.message, (fault) => wake(UNIT, combineFaults(refused, fault)), act.context.actor, act.rcOwner, act);
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
1899
2758
|
const cancel = act.context.cancel;
|
|
1900
2759
|
const stop = this.sendStream(sink, value, term.detail.park, act.context.actor, (status, fault) => {
|
|
2760
|
+
if (status === 0)
|
|
2761
|
+
for (const lease of act.managedInputs.splice(0))
|
|
2762
|
+
lease.release();
|
|
1901
2763
|
wake(UNIT, fault, status);
|
|
1902
2764
|
}, act.rcOwner);
|
|
1903
2765
|
act.context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
@@ -1915,8 +2777,10 @@ class ExecutorV1 {
|
|
|
1915
2777
|
return;
|
|
1916
2778
|
}
|
|
1917
2779
|
let cancelWait = () => { };
|
|
1918
|
-
|
|
1919
|
-
|
|
2780
|
+
let releaseEnvelope = () => { };
|
|
2781
|
+
const wake = this.park(act, term, () => cancelWait(), undefined, () => releaseEnvelope());
|
|
2782
|
+
cancelWait = this.pipes.receive(stream, term.detail.park, (value, fault, released) => {
|
|
2783
|
+
releaseEnvelope = released ?? (() => { });
|
|
1920
2784
|
if (fault !== null)
|
|
1921
2785
|
wake(UNIT, { kind: "panic", message: fault });
|
|
1922
2786
|
else
|
|
@@ -1925,9 +2789,17 @@ class ExecutorV1 {
|
|
|
1925
2789
|
return;
|
|
1926
2790
|
}
|
|
1927
2791
|
case "Await": {
|
|
1928
|
-
const
|
|
2792
|
+
const handle = this.boundary(act, term.inputs[0]);
|
|
2793
|
+
const task = this.taskFor(handle);
|
|
1929
2794
|
let unwatch = () => { };
|
|
1930
|
-
|
|
2795
|
+
let resultLease;
|
|
2796
|
+
const wake = this.park(act, term, () => unwatch(), undefined, () => {
|
|
2797
|
+
resultLease?.release();
|
|
2798
|
+
task.handles.delete(handle);
|
|
2799
|
+
this.managed.retire(handle);
|
|
2800
|
+
task.budget?.changed();
|
|
2801
|
+
this.releaseTaskState(task);
|
|
2802
|
+
});
|
|
1931
2803
|
const ready = () => {
|
|
1932
2804
|
if (!task.done) {
|
|
1933
2805
|
this.pendingActorWait(act, [task.actorTarget], false, "ask", (fault) => wake(UNIT, fault));
|
|
@@ -1937,7 +2809,9 @@ class ExecutorV1 {
|
|
|
1937
2809
|
wake(UNIT, task.fault);
|
|
1938
2810
|
else {
|
|
1939
2811
|
const value = task.value;
|
|
2812
|
+
resultLease = this.retainTaskResult(task, act.context.actor?.budget);
|
|
1940
2813
|
task.value = UNIT;
|
|
2814
|
+
task.taken = true;
|
|
1941
2815
|
wake(value);
|
|
1942
2816
|
}
|
|
1943
2817
|
};
|
|
@@ -1997,12 +2871,28 @@ class ExecutorV1 {
|
|
|
1997
2871
|
const wake = this.park(act, term, () => unwatch.forEach((remove) => remove()));
|
|
1998
2872
|
let cancelTimer;
|
|
1999
2873
|
const choose = () => {
|
|
2000
|
-
const ready =
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2874
|
+
const ready = [];
|
|
2875
|
+
for (const [index, value] of sources.entries()) {
|
|
2876
|
+
if (value.kind === "stream") {
|
|
2877
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2878
|
+
if (adapter)
|
|
2879
|
+
this.pollGeneratorStream(adapter);
|
|
2880
|
+
const readable = adapter
|
|
2881
|
+
? adapter.pending !== null ||
|
|
2882
|
+
adapter.generator.done ||
|
|
2883
|
+
adapter.closed
|
|
2884
|
+
: this.pipes.readable(value);
|
|
2885
|
+
if (readable)
|
|
2886
|
+
ready.push({ index, order: 0 });
|
|
2887
|
+
}
|
|
2888
|
+
else {
|
|
2889
|
+
const task = this.taskFor(value);
|
|
2890
|
+
if (task.done)
|
|
2891
|
+
ready.push({ index, order: task.completedAt });
|
|
2892
|
+
}
|
|
2893
|
+
if (ready.length && term.detail.order !== "completion")
|
|
2894
|
+
break;
|
|
2895
|
+
}
|
|
2006
2896
|
if (term.detail.order === "completion")
|
|
2007
2897
|
ready.sort((a, b) => a.order - b.order);
|
|
2008
2898
|
if (ready[0]) {
|
|
@@ -2022,8 +2912,15 @@ class ExecutorV1 {
|
|
|
2022
2912
|
cancelTimer = this.scheduler.after(duration.value, () => wake({ kind: "i64", value: BigInt(sources.length) }));
|
|
2023
2913
|
}
|
|
2024
2914
|
sources.forEach((value) => {
|
|
2025
|
-
if (value.kind === "stream")
|
|
2026
|
-
|
|
2915
|
+
if (value.kind === "stream") {
|
|
2916
|
+
const adapter = this.generatorStreams.get(value.channelId);
|
|
2917
|
+
if (adapter) {
|
|
2918
|
+
adapter.observers.add(choose);
|
|
2919
|
+
unwatch.push(() => adapter.observers.delete(choose));
|
|
2920
|
+
}
|
|
2921
|
+
else
|
|
2922
|
+
unwatch.push(this.pipes.observe(value, choose));
|
|
2923
|
+
}
|
|
2027
2924
|
else {
|
|
2028
2925
|
const task = this.taskFor(value);
|
|
2029
2926
|
task.changed.push(choose);
|
|
@@ -2178,6 +3075,10 @@ class ExecutorV1 {
|
|
|
2178
3075
|
let cancelTimer = () => { };
|
|
2179
3076
|
const wake = this.park(act, term, () => cancelTimer());
|
|
2180
3077
|
cancelTimer = this.scheduler.after(nanos, () => wake(UNIT));
|
|
3078
|
+
// Admission can publish a fault synchronously before this sleep parks.
|
|
3079
|
+
// Deliver that latch after arming, so cancellation also removes the timer.
|
|
3080
|
+
if (act.context.pendingCancellation)
|
|
3081
|
+
act.context.cancel?.(act.context.pendingCancellation);
|
|
2181
3082
|
return;
|
|
2182
3083
|
}
|
|
2183
3084
|
default: {
|
|
@@ -2203,13 +3104,30 @@ class ExecutorV1 {
|
|
|
2203
3104
|
message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
|
|
2204
3105
|
});
|
|
2205
3106
|
}
|
|
2206
|
-
park(act, term, cleanup = () => { }, accepted = () => { }) {
|
|
3107
|
+
park(act, term, cleanup = () => { }, accepted = () => { }, drained = () => { }) {
|
|
2207
3108
|
this.running = false;
|
|
2208
3109
|
act.context.parked = true;
|
|
2209
3110
|
let pending = null;
|
|
3111
|
+
let pendingLease;
|
|
2210
3112
|
const resume = (value, fault = null, edge = 0) => {
|
|
2211
3113
|
if (pending)
|
|
2212
3114
|
return false;
|
|
3115
|
+
if (!fault &&
|
|
3116
|
+
act.context.actor?.budget &&
|
|
3117
|
+
term.result &&
|
|
3118
|
+
term.result !== "never") {
|
|
3119
|
+
const type = term.result.managed_type;
|
|
3120
|
+
if (type == null)
|
|
3121
|
+
throw new Error("suspended result lacks its managed descriptor");
|
|
3122
|
+
try {
|
|
3123
|
+
pendingLease = act.context.actor.budget.retain({ value, type });
|
|
3124
|
+
}
|
|
3125
|
+
catch (error) {
|
|
3126
|
+
if (!(error instanceof HeapExceeded))
|
|
3127
|
+
throw error;
|
|
3128
|
+
fault = { kind: "trap", trap: "heap_exceeded" };
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
2213
3131
|
pending = { value, fault, edge };
|
|
2214
3132
|
act.context.parked = false;
|
|
2215
3133
|
if (act.context.actor?.active === act.context)
|
|
@@ -2236,6 +3154,8 @@ class ExecutorV1 {
|
|
|
2236
3154
|
throw new Error(`${term.kind} has no resume edge`);
|
|
2237
3155
|
this.takeEdge(act, edge);
|
|
2238
3156
|
}
|
|
3157
|
+
pendingLease?.release();
|
|
3158
|
+
drained();
|
|
2239
3159
|
this.runFrame(act);
|
|
2240
3160
|
};
|
|
2241
3161
|
// A close can arrive after readiness but before this frame runs. Keep
|
|
@@ -2440,7 +3360,16 @@ class ExecutorV1 {
|
|
|
2440
3360
|
adapter.waiting = undefined;
|
|
2441
3361
|
this.closeGenerator(generator, (closeFault) => cancel?.(closeFault ?? fault));
|
|
2442
3362
|
};
|
|
2443
|
-
|
|
3363
|
+
this.pollGeneratorStream(adapter);
|
|
3364
|
+
}
|
|
3365
|
+
/** Readiness drives the same producer as recv but leaves a published item
|
|
3366
|
+
* retained until the winning receive. Losing observations only detach. */
|
|
3367
|
+
pollGeneratorStream(adapter) {
|
|
3368
|
+
const generator = adapter.generator;
|
|
3369
|
+
if (adapter.receiving ||
|
|
3370
|
+
adapter.pending !== null ||
|
|
3371
|
+
generator.done ||
|
|
3372
|
+
adapter.closed)
|
|
2444
3373
|
return;
|
|
2445
3374
|
adapter.receiving = true;
|
|
2446
3375
|
generator.receive = (value, fault) => {
|
|
@@ -2457,6 +3386,8 @@ class ExecutorV1 {
|
|
|
2457
3386
|
waiting(value, fault);
|
|
2458
3387
|
else
|
|
2459
3388
|
adapter.pending = value;
|
|
3389
|
+
for (const changed of [...adapter.observers])
|
|
3390
|
+
changed();
|
|
2460
3391
|
};
|
|
2461
3392
|
if (generator.started) {
|
|
2462
3393
|
const resume = generator.resume;
|
|
@@ -2499,7 +3430,7 @@ class ExecutorV1 {
|
|
|
2499
3430
|
act.fault = combineFaults(act.fault, fault);
|
|
2500
3431
|
if (!synchronous)
|
|
2501
3432
|
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
2502
|
-
}, act.context.actor, act.rcOwner);
|
|
3433
|
+
}, act.context.actor, act.rcOwner, act);
|
|
2503
3434
|
synchronous = false;
|
|
2504
3435
|
if (!completed)
|
|
2505
3436
|
this.running = false;
|
|
@@ -2537,7 +3468,7 @@ class ExecutorV1 {
|
|
|
2537
3468
|
else
|
|
2538
3469
|
generator.frame.context.cancel?.();
|
|
2539
3470
|
}
|
|
2540
|
-
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner) {
|
|
3471
|
+
closeValueAsync(value, fault, done, actor = this.current.context.actor, rcOwner = actor?.rcOwner ?? this.current.rcOwner, retainedBy) {
|
|
2541
3472
|
const pending = [value];
|
|
2542
3473
|
let firstFault = null;
|
|
2543
3474
|
const next = (failure = null) => {
|
|
@@ -2546,10 +3477,38 @@ class ExecutorV1 {
|
|
|
2546
3477
|
// must disclose that failure instead of publishing a clean end.
|
|
2547
3478
|
const currentFault = fault ?? this.faultText(actor?.crashing ?? firstFault);
|
|
2548
3479
|
while (pending.length) {
|
|
2549
|
-
const
|
|
3480
|
+
const entry = pending.pop();
|
|
3481
|
+
if ("managedRelease" in entry) {
|
|
3482
|
+
entry.managedRelease.release();
|
|
3483
|
+
continue;
|
|
3484
|
+
}
|
|
3485
|
+
if ("taskTaken" in entry) {
|
|
3486
|
+
entry.taskTaken.value = UNIT;
|
|
3487
|
+
entry.taskTaken.taken = true;
|
|
3488
|
+
entry.taskTaken.managedResult = undefined;
|
|
3489
|
+
entry.taskTaken.managedReply = undefined;
|
|
3490
|
+
continue;
|
|
3491
|
+
}
|
|
3492
|
+
if ("retire" in entry) {
|
|
3493
|
+
if (entry.retire.kind === "stream" &&
|
|
3494
|
+
!this.generatorStreams.has(entry.retire.channelId))
|
|
3495
|
+
this.pipes.finishClose(entry.retire);
|
|
3496
|
+
this.managed.retire(entry.retire);
|
|
3497
|
+
if (entry.retire.kind === "stream")
|
|
3498
|
+
this.generatorStreams.delete(entry.retire.channelId);
|
|
3499
|
+
actor?.budget?.changed();
|
|
3500
|
+
if (entry.retire.kind === "task") {
|
|
3501
|
+
const task = this.tasks.get(entry.retire.id);
|
|
3502
|
+
if (task)
|
|
3503
|
+
this.releaseTaskState(task);
|
|
3504
|
+
}
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
const value = entry;
|
|
3508
|
+
pending.push({ retire: value });
|
|
2550
3509
|
switch (value.kind) {
|
|
2551
3510
|
case "encoding":
|
|
2552
|
-
value
|
|
3511
|
+
releaseEncoding(value);
|
|
2553
3512
|
break;
|
|
2554
3513
|
case "string":
|
|
2555
3514
|
value.release();
|
|
@@ -2558,6 +3517,21 @@ class ExecutorV1 {
|
|
|
2558
3517
|
if (!value.closed) {
|
|
2559
3518
|
value.closed = true;
|
|
2560
3519
|
if (value.cell && !value.cell.dying && --value.cell.refs === 0) {
|
|
3520
|
+
if (actor?.budget) {
|
|
3521
|
+
const row = this.managed.row(this.managed.type(value));
|
|
3522
|
+
if (row.kind !== "shared")
|
|
3523
|
+
throw new Error("Rc has no canonical payload descriptor");
|
|
3524
|
+
pending.push({
|
|
3525
|
+
managedRelease: actor.budget.retain({
|
|
3526
|
+
value: value.cell.value,
|
|
3527
|
+
type: row.payload,
|
|
3528
|
+
slots: true,
|
|
3529
|
+
identity: value.cell,
|
|
3530
|
+
}),
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
value.cell.managedLease?.release();
|
|
3534
|
+
value.cell.managedLease = undefined;
|
|
2561
3535
|
value.cell.owner?.blocks.delete(value.cell);
|
|
2562
3536
|
value.cell.owner = null;
|
|
2563
3537
|
pending.push(value.cell.value);
|
|
@@ -2586,17 +3560,18 @@ class ExecutorV1 {
|
|
|
2586
3560
|
const resource = this.pkg.resources?.[value.resource];
|
|
2587
3561
|
if (resource?.kind !== "opaque")
|
|
2588
3562
|
throw new Error("opaque owner lost its selected close");
|
|
2589
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
3563
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
|
|
2590
3564
|
return;
|
|
2591
3565
|
}
|
|
2592
3566
|
case "task": {
|
|
2593
3567
|
// Dropping a handle does not cancel scoped work. Join drains the
|
|
2594
3568
|
// task, and scope close releases any result that was never taken.
|
|
2595
3569
|
const task = this.tasks.get(value.id);
|
|
2596
|
-
|
|
3570
|
+
const owned = task?.handles.delete(value);
|
|
3571
|
+
if (task?.done && (!owned || !task.scopeAlive)) {
|
|
2597
3572
|
const result = task.value;
|
|
2598
|
-
|
|
2599
|
-
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner);
|
|
3573
|
+
pending.push({ taskTaken: task });
|
|
3574
|
+
this.closeValueAsync(result, currentFault, (fault) => next(fault ?? (task.close ? task.fault : null)), actor, rcOwner, retainedBy);
|
|
2600
3575
|
return;
|
|
2601
3576
|
}
|
|
2602
3577
|
if (task?.close) {
|
|
@@ -2624,7 +3599,7 @@ class ExecutorV1 {
|
|
|
2624
3599
|
if (this.consumedResources.has(value))
|
|
2625
3600
|
break;
|
|
2626
3601
|
if (!this.closingResources.has(value)) {
|
|
2627
|
-
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner);
|
|
3602
|
+
this.invokeFrame(actor, resource.close, [value], () => next(), next, rcOwner, false, retainedBy);
|
|
2628
3603
|
return;
|
|
2629
3604
|
}
|
|
2630
3605
|
this.consumedResources.add(value);
|
|
@@ -2636,12 +3611,11 @@ class ExecutorV1 {
|
|
|
2636
3611
|
pending.push(...[...value.payload].reverse());
|
|
2637
3612
|
break;
|
|
2638
3613
|
case "vector":
|
|
2639
|
-
pending.push(...
|
|
3614
|
+
pending.push(...releaseVector(value).reverse());
|
|
2640
3615
|
break;
|
|
2641
3616
|
case "stream": {
|
|
2642
3617
|
const adapter = this.generatorStreams.get(value.channelId);
|
|
2643
3618
|
if (adapter) {
|
|
2644
|
-
this.generatorStreams.delete(value.channelId);
|
|
2645
3619
|
adapter.closed = true;
|
|
2646
3620
|
if (adapter.pending !== null)
|
|
2647
3621
|
pending.push(adapter.pending);
|
|
@@ -2687,7 +3661,7 @@ class ExecutorV1 {
|
|
|
2687
3661
|
}
|
|
2688
3662
|
closeValue(value, fault) {
|
|
2689
3663
|
if (value.kind === "encoding")
|
|
2690
|
-
value
|
|
3664
|
+
releaseEncoding(value);
|
|
2691
3665
|
if (value.kind === "host_resource")
|
|
2692
3666
|
this.closeHostResource(value);
|
|
2693
3667
|
if (value.kind === "string")
|
|
@@ -2703,11 +3677,34 @@ class ExecutorV1 {
|
|
|
2703
3677
|
for (const field of value.payload)
|
|
2704
3678
|
this.closeValue(field, fault);
|
|
2705
3679
|
else if (value.kind === "vector")
|
|
2706
|
-
for (const item of value
|
|
3680
|
+
for (const item of releaseVector(value))
|
|
2707
3681
|
this.closeValue(item, fault);
|
|
3682
|
+
else if (value.kind === "rc" && !value.closed) {
|
|
3683
|
+
value.closed = true;
|
|
3684
|
+
const cell = value.cell;
|
|
3685
|
+
if (cell && !cell.dying && --cell.refs === 0) {
|
|
3686
|
+
cell.dying = true;
|
|
3687
|
+
this.closeValue(cell.value, fault);
|
|
3688
|
+
cell.value = UNIT;
|
|
3689
|
+
cell.managedLease?.release();
|
|
3690
|
+
cell.managedLease = undefined;
|
|
3691
|
+
cell.owner?.blocks.delete(cell);
|
|
3692
|
+
cell.owner = null;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
else if (value.kind === "weak" && !value.closed) {
|
|
3696
|
+
value.closed = true;
|
|
3697
|
+
value.cell.weak--;
|
|
3698
|
+
}
|
|
3699
|
+
this.managed.retire(value);
|
|
2708
3700
|
}
|
|
2709
|
-
newTask() {
|
|
3701
|
+
newTask(budget, type, memberBytes = 0n, scopeAlive = false) {
|
|
2710
3702
|
const task = {
|
|
3703
|
+
handles: new Set(),
|
|
3704
|
+
scopeAlive,
|
|
3705
|
+
budget,
|
|
3706
|
+
memberBytes,
|
|
3707
|
+
managedType: type,
|
|
2711
3708
|
id: this.ids.task(),
|
|
2712
3709
|
grouped: false,
|
|
2713
3710
|
done: false,
|
|
@@ -2720,8 +3717,44 @@ class ExecutorV1 {
|
|
|
2720
3717
|
changed: [],
|
|
2721
3718
|
};
|
|
2722
3719
|
this.tasks.set(task.id, task);
|
|
3720
|
+
if (budget) {
|
|
3721
|
+
if (type == null)
|
|
3722
|
+
throw new Error("task has no managed result descriptor");
|
|
3723
|
+
const row = this.managed.row(type);
|
|
3724
|
+
if (row.kind === "provider" && row.provider === "task")
|
|
3725
|
+
task.managedLease = budget.retain({
|
|
3726
|
+
value: { kind: "task", id: task.id },
|
|
3727
|
+
type,
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
2723
3730
|
return task;
|
|
2724
3731
|
}
|
|
3732
|
+
releaseTaskState(task) {
|
|
3733
|
+
if (task.scopeAlive || task.handles.size || !this.tasks.has(task.id))
|
|
3734
|
+
return;
|
|
3735
|
+
this.releaseRaceMembers(task);
|
|
3736
|
+
task.managedLease?.release();
|
|
3737
|
+
task.managedLease = undefined;
|
|
3738
|
+
this.tasks.delete(task.id);
|
|
3739
|
+
task.budget?.changed();
|
|
3740
|
+
}
|
|
3741
|
+
releaseRaceMembers(task) {
|
|
3742
|
+
const members = task.members;
|
|
3743
|
+
if (!members)
|
|
3744
|
+
return;
|
|
3745
|
+
task.members = undefined;
|
|
3746
|
+
task.memberBytes = 0n;
|
|
3747
|
+
task.budget?.changed();
|
|
3748
|
+
for (const value of members) {
|
|
3749
|
+
if (value.kind !== "task")
|
|
3750
|
+
continue;
|
|
3751
|
+
const member = this.tasks.get(value.id);
|
|
3752
|
+
member?.handles.delete(value);
|
|
3753
|
+
this.managed.retire(value);
|
|
3754
|
+
if (member)
|
|
3755
|
+
this.releaseTaskState(member);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
2725
3758
|
taskFor(value) {
|
|
2726
3759
|
if (value.kind !== "task")
|
|
2727
3760
|
throw new Error(`expected a task, got ${value.kind}`);
|
|
@@ -2734,21 +3767,74 @@ class ExecutorV1 {
|
|
|
2734
3767
|
if (task.done)
|
|
2735
3768
|
return;
|
|
2736
3769
|
task.done = true;
|
|
3770
|
+
task.resultInitialized = true;
|
|
2737
3771
|
task.value = value;
|
|
2738
3772
|
task.fault = fault;
|
|
3773
|
+
task.budget?.changed();
|
|
2739
3774
|
task.completedAt = ++this.completedTasks;
|
|
2740
3775
|
for (const changed of [...task.changed])
|
|
2741
3776
|
changed();
|
|
2742
3777
|
}
|
|
2743
|
-
|
|
3778
|
+
retainTaskResult(task, budget) {
|
|
3779
|
+
if (!budget)
|
|
3780
|
+
return undefined;
|
|
3781
|
+
if (task.fault || (task.actorError && task.actorError !== "Failed"))
|
|
3782
|
+
return undefined;
|
|
3783
|
+
const row = task.managedType == null ? undefined : this.managed.row(task.managedType);
|
|
3784
|
+
if (row?.kind === "actor_call")
|
|
3785
|
+
return budget.retain({
|
|
3786
|
+
value: task.managedResult,
|
|
3787
|
+
type: row.completion,
|
|
3788
|
+
});
|
|
3789
|
+
if (!row || row.kind !== "provider" || row.children[0] == null)
|
|
3790
|
+
throw new Error("task result lacks its canonical payload descriptor");
|
|
3791
|
+
return budget.retain({
|
|
3792
|
+
value: task.managedResult ?? task.value,
|
|
3793
|
+
type: row.children[0],
|
|
3794
|
+
});
|
|
3795
|
+
}
|
|
3796
|
+
managedCompletion(task, value, error, envelope) {
|
|
3797
|
+
if (task.managedType == null ||
|
|
3798
|
+
value === null ||
|
|
3799
|
+
(error !== null && error !== "Failed"))
|
|
3800
|
+
return undefined;
|
|
3801
|
+
const provider = this.managed.row(task.managedType);
|
|
3802
|
+
if (provider.kind !== "actor_call")
|
|
3803
|
+
throw new Error("actor completion has no canonical provider descriptor");
|
|
3804
|
+
const type = provider.completion;
|
|
3805
|
+
const result = this.managed.row(type);
|
|
3806
|
+
if (result.kind !== "variant")
|
|
3807
|
+
throw new Error("actor completion has no canonical result cases");
|
|
3808
|
+
const err = runtimeTag(this.pkg.variants[result.shape], "ResultErr");
|
|
3809
|
+
const failure = this.managed.row(result.cases[err][0]);
|
|
3810
|
+
if (failure.kind !== "variant")
|
|
3811
|
+
throw new Error("actor completion has no canonical error cases");
|
|
3812
|
+
if (!envelope)
|
|
3813
|
+
throw new Error("actor completion lost its actual handler envelope");
|
|
3814
|
+
return {
|
|
3815
|
+
value: this.completion(result.shape, failure.shape, value, error),
|
|
3816
|
+
reply: { value: envelope, type: provider.reply },
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
invokeFrame(actor, callable, args, returned, failed, rcOwner = actor?.rcOwner ?? null, inline = false, retainedBy) {
|
|
2744
3820
|
const context = {
|
|
2745
3821
|
id: actor?.id ?? "actor:root",
|
|
2746
3822
|
actor,
|
|
3823
|
+
retainsTransferableFrame: !!retainedBy?.context.generator ||
|
|
3824
|
+
retainedBy?.context.retainsTransferableFrame,
|
|
2747
3825
|
rcOwner,
|
|
2748
|
-
returned,
|
|
2749
|
-
|
|
3826
|
+
returned: (value, released) => {
|
|
3827
|
+
retainedBy?.managedChildren.delete(frame);
|
|
3828
|
+
returned(value);
|
|
3829
|
+
released();
|
|
3830
|
+
},
|
|
3831
|
+
failed: (fault) => {
|
|
3832
|
+
retainedBy?.managedChildren.delete(frame);
|
|
3833
|
+
failed(fault);
|
|
3834
|
+
},
|
|
2750
3835
|
};
|
|
2751
3836
|
const frame = this.activate(this.functionAt(callable), args, null, null, undefined, null, context);
|
|
3837
|
+
retainedBy?.managedChildren.add(frame);
|
|
2752
3838
|
let cancelled = null;
|
|
2753
3839
|
context.cancel = (fault = { kind: "panic", message: "task cancelled", cancelled: true }) => {
|
|
2754
3840
|
cancelled = fault;
|
|
@@ -2840,6 +3926,7 @@ class ExecutorV1 {
|
|
|
2840
3926
|
mailbox: [],
|
|
2841
3927
|
timers: new Set(),
|
|
2842
3928
|
disposals: [],
|
|
3929
|
+
disposalLeases: [],
|
|
2843
3930
|
admission: [],
|
|
2844
3931
|
busy: true,
|
|
2845
3932
|
constructing: true,
|
|
@@ -2883,6 +3970,8 @@ class ExecutorV1 {
|
|
|
2883
3970
|
const retained = combineFaults(primary, stateFault);
|
|
2884
3971
|
this.sweepRcOwner(actor.rcOwner, actor, (sweepFault) => {
|
|
2885
3972
|
actor.completed = true;
|
|
3973
|
+
actor.stateLease?.release();
|
|
3974
|
+
actor.stateLease = undefined;
|
|
2886
3975
|
for (const closed of actor.closed.splice(0))
|
|
2887
3976
|
closed();
|
|
2888
3977
|
this.raiseFault(act, combineFaults(retained, sweepFault), term.unwind);
|
|
@@ -2919,6 +4008,24 @@ class ExecutorV1 {
|
|
|
2919
4008
|
else
|
|
2920
4009
|
started();
|
|
2921
4010
|
};
|
|
4011
|
+
if (layout.max_heap_bytes != null) {
|
|
4012
|
+
actor.budget = new ManagedBudget(this.managed, BigInt(layout.max_heap_bytes), (root) => this.adoptManaged(root, actor));
|
|
4013
|
+
if (layout.state_type == null)
|
|
4014
|
+
throw new Error("limited actor state lacks a managed descriptor");
|
|
4015
|
+
try {
|
|
4016
|
+
actor.stateLease = actor.budget.retain({
|
|
4017
|
+
value: actor.state,
|
|
4018
|
+
type: layout.state_type,
|
|
4019
|
+
slots: true,
|
|
4020
|
+
});
|
|
4021
|
+
}
|
|
4022
|
+
catch (error) {
|
|
4023
|
+
if (!(error instanceof HeapExceeded))
|
|
4024
|
+
throw error;
|
|
4025
|
+
failed({ kind: "trap", trap: "heap_exceeded" });
|
|
4026
|
+
return;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
2922
4029
|
if (layout.init != null)
|
|
2923
4030
|
actor.active = this.invokeFrame(actor, layout.init, [actor.state, ...args.slice(supplied)], start, failed, actor.rcOwner, true);
|
|
2924
4031
|
else
|
|
@@ -3026,7 +4133,9 @@ class ExecutorV1 {
|
|
|
3026
4133
|
complete: (_value, _error, drained) => drained?.(null),
|
|
3027
4134
|
};
|
|
3028
4135
|
const status = this.admitActorMessage(actor, message);
|
|
3029
|
-
if (status === "full" ||
|
|
4136
|
+
if (status === "full" ||
|
|
4137
|
+
status === "closed" ||
|
|
4138
|
+
status === "trapped") {
|
|
3030
4139
|
actor.disposals.push(payload);
|
|
3031
4140
|
timer.discarded();
|
|
3032
4141
|
this.dispatchActor(actor);
|
|
@@ -3064,7 +4173,21 @@ class ExecutorV1 {
|
|
|
3064
4173
|
const handler = actor.layout.handlers.find((handler) => handler.message_id === operation.message && handler.streams);
|
|
3065
4174
|
if (!handler)
|
|
3066
4175
|
throw new Error("stream request has no producer handler");
|
|
4176
|
+
let managedLease;
|
|
4177
|
+
try {
|
|
4178
|
+
managedLease = this.acquireActorMessage(actor, handler, fields);
|
|
4179
|
+
}
|
|
4180
|
+
catch (error) {
|
|
4181
|
+
if (!(error instanceof HeapExceeded))
|
|
4182
|
+
throw error;
|
|
4183
|
+
this.crashActor(actor, { kind: "trap", trap: "heap_exceeded" }, handler);
|
|
4184
|
+
this.closeValue(payload, error.message);
|
|
4185
|
+
this.completeShim(act, term, UNIT);
|
|
4186
|
+
this.scheduler.enqueue(act.context.id, () => this.runFrame(act));
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
3067
4189
|
actor.mailbox.push({
|
|
4190
|
+
managedLease,
|
|
3068
4191
|
caller,
|
|
3069
4192
|
handler,
|
|
3070
4193
|
payload: fields,
|
|
@@ -3085,8 +4208,29 @@ class ExecutorV1 {
|
|
|
3085
4208
|
return;
|
|
3086
4209
|
}
|
|
3087
4210
|
case "call_start": {
|
|
3088
|
-
const task = this.newTask(
|
|
3089
|
-
|
|
4211
|
+
const task = this.newTask(act.context.actor?.budget, term.result && term.result !== "never"
|
|
4212
|
+
? (term.result.managed_type ?? undefined)
|
|
4213
|
+
: undefined);
|
|
4214
|
+
const cancelRequest = this.ask(operation.protocol, args, (value, error, closeFault, envelope) => {
|
|
4215
|
+
const reply = this.managedCompletion(task, value, error, envelope);
|
|
4216
|
+
if (reply && !closeFault && act.context.actor?.budget) {
|
|
4217
|
+
try {
|
|
4218
|
+
act.context.actor.budget.checkPayload(this.managed.width(reply.reply.type), [reply.reply]);
|
|
4219
|
+
}
|
|
4220
|
+
catch (failure) {
|
|
4221
|
+
if (!(failure instanceof HeapExceeded))
|
|
4222
|
+
throw failure;
|
|
4223
|
+
task.actorError = "Trapped";
|
|
4224
|
+
this.settleTask(task, UNIT, {
|
|
4225
|
+
kind: "trap",
|
|
4226
|
+
trap: "heap_exceeded",
|
|
4227
|
+
});
|
|
4228
|
+
this.failActorBudget(act.context.actor);
|
|
4229
|
+
return false;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
task.managedResult = reply?.value;
|
|
4233
|
+
task.managedReply = reply?.reply.value;
|
|
3090
4234
|
task.actorError = error;
|
|
3091
4235
|
this.settleTask(task, value ?? UNIT, closeFault ?? null);
|
|
3092
4236
|
return true;
|
|
@@ -3108,8 +4252,20 @@ class ExecutorV1 {
|
|
|
3108
4252
|
throw new Error("actor completion taken before readiness");
|
|
3109
4253
|
act.fault ??= task.fault;
|
|
3110
4254
|
const value = task.value;
|
|
4255
|
+
const lease = this.retainTaskResult(task, act.context.actor?.budget);
|
|
4256
|
+
if (lease)
|
|
4257
|
+
act.managedInputs.push(lease);
|
|
3111
4258
|
task.value = UNIT;
|
|
3112
|
-
|
|
4259
|
+
task.taken = true;
|
|
4260
|
+
const result = task.managedResult ??
|
|
4261
|
+
this.completion(term.result_shape, term.error_shape, value, task.actorError);
|
|
4262
|
+
task.managedResult = undefined;
|
|
4263
|
+
task.managedReply = undefined;
|
|
4264
|
+
this.completeShim(act, term, result);
|
|
4265
|
+
task.handles.delete(args[0]);
|
|
4266
|
+
this.managed.retire(args[0]);
|
|
4267
|
+
task.budget?.changed();
|
|
4268
|
+
this.releaseTaskState(task);
|
|
3113
4269
|
return;
|
|
3114
4270
|
}
|
|
3115
4271
|
case "submit": {
|
|
@@ -3162,6 +4318,15 @@ class ExecutorV1 {
|
|
|
3162
4318
|
complete: (_value, _error, drained) => drained?.(null),
|
|
3163
4319
|
};
|
|
3164
4320
|
const status = this.admitActorMessage(actor, message);
|
|
4321
|
+
if (status === "trapped") {
|
|
4322
|
+
this.closeValueAsync(payload, "actor heap limit exceeded", (fault) => {
|
|
4323
|
+
act.fault ??= fault;
|
|
4324
|
+
finish(this.variant(term.result_shape, "ResultErr", [
|
|
4325
|
+
this.variant(term.error_shape, "ActorErrorTrapped"),
|
|
4326
|
+
]));
|
|
4327
|
+
}, act.context.actor, act.rcOwner);
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
3165
4330
|
if (status === "full") {
|
|
3166
4331
|
if (operation.policy === "drop_newest") {
|
|
3167
4332
|
let settled = false;
|
|
@@ -3463,6 +4628,7 @@ class ExecutorV1 {
|
|
|
3463
4628
|
let admitted = false;
|
|
3464
4629
|
let closing = false;
|
|
3465
4630
|
const owner = this.current.context.actor;
|
|
4631
|
+
const sender = this.current;
|
|
3466
4632
|
const rcOwner = this.current.rcOwner;
|
|
3467
4633
|
const deadline = this.requestDeadline(this.current);
|
|
3468
4634
|
let cancelTimer;
|
|
@@ -3471,11 +4637,12 @@ class ExecutorV1 {
|
|
|
3471
4637
|
? { kind: "Actor", identity: owner.identity }
|
|
3472
4638
|
: { kind: "Task" };
|
|
3473
4639
|
let receiver = null;
|
|
3474
|
-
const finish = (value, error, drained) => {
|
|
4640
|
+
const finish = (value, error, drained, envelope) => {
|
|
3475
4641
|
const alreadySettled = settled;
|
|
3476
4642
|
settled = true;
|
|
3477
4643
|
cancelTimer?.();
|
|
3478
|
-
if (alreadySettled ||
|
|
4644
|
+
if (alreadySettled ||
|
|
4645
|
+
complete(value, error, undefined, envelope) === false) {
|
|
3479
4646
|
// The finishing receiver still owns a reply that its caller no
|
|
3480
4647
|
// longer accepts. Its next turn must wait for this release.
|
|
3481
4648
|
this.closeValueAsync(value ?? UNIT, null, (fault) => {
|
|
@@ -3582,9 +4749,23 @@ class ExecutorV1 {
|
|
|
3582
4749
|
}
|
|
3583
4750
|
return;
|
|
3584
4751
|
}
|
|
4752
|
+
let managedLease;
|
|
4753
|
+
try {
|
|
4754
|
+
managedLease = this.acquireActorMessage(actor, handler, payload);
|
|
4755
|
+
}
|
|
4756
|
+
catch (error) {
|
|
4757
|
+
if (!(error instanceof HeapExceeded))
|
|
4758
|
+
throw error;
|
|
4759
|
+
this.failActorBudget(actor);
|
|
4760
|
+
reject("Trapped");
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
3585
4763
|
admitted = true;
|
|
4764
|
+
for (const lease of sender.managedInputs.splice(0))
|
|
4765
|
+
lease.release();
|
|
3586
4766
|
receiver = actor;
|
|
3587
4767
|
message = {
|
|
4768
|
+
managedLease,
|
|
3588
4769
|
caller,
|
|
3589
4770
|
handler,
|
|
3590
4771
|
payload,
|
|
@@ -3607,6 +4788,8 @@ class ExecutorV1 {
|
|
|
3607
4788
|
if (index < 0)
|
|
3608
4789
|
return;
|
|
3609
4790
|
receiver.mailbox.splice(index, 1);
|
|
4791
|
+
message.managedLease?.release();
|
|
4792
|
+
message.managedLease = undefined;
|
|
3610
4793
|
admitted = false;
|
|
3611
4794
|
for (const admit of receiver.admission.splice(0))
|
|
3612
4795
|
admit();
|
|
@@ -3634,13 +4817,56 @@ class ExecutorV1 {
|
|
|
3634
4817
|
closeRequest(done);
|
|
3635
4818
|
};
|
|
3636
4819
|
}
|
|
4820
|
+
acquireActorMessage(actor, handler, payload) {
|
|
4821
|
+
const params = this.functionAt(handler.callable).params.slice(1);
|
|
4822
|
+
if (params.length !== payload.length)
|
|
4823
|
+
throw new Error("typed message payload differs from its handler");
|
|
4824
|
+
if (!actor.budget &&
|
|
4825
|
+
!this.pipes.hasManagedReceivers &&
|
|
4826
|
+
params.every((param) => param.managed_type == null ||
|
|
4827
|
+
!this.managed.needsAdoption(param.managed_type)))
|
|
4828
|
+
return undefined;
|
|
4829
|
+
const roots = params.flatMap((param, index) => {
|
|
4830
|
+
if (param.managed_type == null) {
|
|
4831
|
+
if (!actor.budget)
|
|
4832
|
+
return [];
|
|
4833
|
+
throw new Error("message parameter lacks its managed descriptor");
|
|
4834
|
+
}
|
|
4835
|
+
return [
|
|
4836
|
+
{
|
|
4837
|
+
value: payload[index],
|
|
4838
|
+
type: param.managed_type,
|
|
4839
|
+
slots: true,
|
|
4840
|
+
},
|
|
4841
|
+
];
|
|
4842
|
+
});
|
|
4843
|
+
if (actor.budget)
|
|
4844
|
+
return actor.budget.retainMany(roots);
|
|
4845
|
+
for (const root of roots)
|
|
4846
|
+
this.adoptManaged(root, actor);
|
|
4847
|
+
return undefined;
|
|
4848
|
+
}
|
|
3637
4849
|
admitActorMessage(actor, message) {
|
|
3638
4850
|
if (!actor.alive || actor.closing)
|
|
3639
4851
|
return "closed";
|
|
3640
4852
|
const full = actor.layout.mailbox_capacity != null &&
|
|
3641
4853
|
actor.mailbox.length >= actor.layout.mailbox_capacity;
|
|
3642
4854
|
let declared = full ? actor.layout.overflow : null;
|
|
4855
|
+
const acquire = () => {
|
|
4856
|
+
try {
|
|
4857
|
+
message.managedLease = this.acquireActorMessage(actor, message.handler, message.payload);
|
|
4858
|
+
}
|
|
4859
|
+
catch (error) {
|
|
4860
|
+
if (!(error instanceof HeapExceeded))
|
|
4861
|
+
throw error;
|
|
4862
|
+
this.failActorBudget(actor);
|
|
4863
|
+
return false;
|
|
4864
|
+
}
|
|
4865
|
+
return true;
|
|
4866
|
+
};
|
|
3643
4867
|
const discard = (lost) => {
|
|
4868
|
+
if (lost.managedLease)
|
|
4869
|
+
actor.disposalLeases.push(lost.managedLease);
|
|
3644
4870
|
actor.disposals.push({
|
|
3645
4871
|
kind: "record",
|
|
3646
4872
|
typeId: "",
|
|
@@ -3658,6 +4884,8 @@ class ExecutorV1 {
|
|
|
3658
4884
|
equals(queued.payload[key.param], message.payload[key.param]))
|
|
3659
4885
|
: -1;
|
|
3660
4886
|
if (queued >= 0) {
|
|
4887
|
+
if (!acquire())
|
|
4888
|
+
return "trapped";
|
|
3661
4889
|
const superseded = actor.mailbox[queued];
|
|
3662
4890
|
actor.mailbox[queued] = message;
|
|
3663
4891
|
discard(superseded);
|
|
@@ -3673,6 +4901,8 @@ class ExecutorV1 {
|
|
|
3673
4901
|
? actor.mailbox.findIndex((queued) => !queued.obligated)
|
|
3674
4902
|
: -1;
|
|
3675
4903
|
if (oldest >= 0) {
|
|
4904
|
+
if (!acquire())
|
|
4905
|
+
return "trapped";
|
|
3676
4906
|
const [evicted] = actor.mailbox.splice(oldest, 1);
|
|
3677
4907
|
actor.mailbox.push(message);
|
|
3678
4908
|
discard(evicted);
|
|
@@ -3680,6 +4910,8 @@ class ExecutorV1 {
|
|
|
3680
4910
|
}
|
|
3681
4911
|
if (full)
|
|
3682
4912
|
return "full";
|
|
4913
|
+
if (!acquire())
|
|
4914
|
+
return "trapped";
|
|
3683
4915
|
actor.mailbox.push(message);
|
|
3684
4916
|
this.trace.snapshot("actor.send", {
|
|
3685
4917
|
actor_id: actor.id,
|
|
@@ -3772,7 +5004,9 @@ class ExecutorV1 {
|
|
|
3772
5004
|
if (actor.disposals.length > 0) {
|
|
3773
5005
|
actor.busy = true;
|
|
3774
5006
|
const values = actor.disposals.splice(0);
|
|
5007
|
+
const leases = actor.disposalLeases.splice(0);
|
|
3775
5008
|
this.closeValueAsync({ kind: "vector", elementType: "message", items: values }, this.faultText(actor.crashing), (fault) => {
|
|
5009
|
+
leases.forEach((lease) => lease.release());
|
|
3776
5010
|
actor.busy = false;
|
|
3777
5011
|
if (fault)
|
|
3778
5012
|
this.crashActor(actor, fault);
|
|
@@ -3816,6 +5050,8 @@ class ExecutorV1 {
|
|
|
3816
5050
|
return;
|
|
3817
5051
|
}
|
|
3818
5052
|
actor.mailbox.shift();
|
|
5053
|
+
message.managedLease?.release();
|
|
5054
|
+
message.managedLease = undefined;
|
|
3819
5055
|
for (const admit of actor.admission.splice(0))
|
|
3820
5056
|
admit();
|
|
3821
5057
|
if (message.requestDeadline !== undefined &&
|
|
@@ -3841,10 +5077,21 @@ class ExecutorV1 {
|
|
|
3841
5077
|
caller: message.caller,
|
|
3842
5078
|
requestDeadline: message.requestDeadline,
|
|
3843
5079
|
reply: message.reply,
|
|
3844
|
-
returned: (value) => {
|
|
5080
|
+
returned: (value, released) => {
|
|
3845
5081
|
unwatchStream();
|
|
3846
5082
|
actor.active = null;
|
|
5083
|
+
if (actor.crashing?.kind === "trap" &&
|
|
5084
|
+
actor.crashing.trap === "heap_exceeded") {
|
|
5085
|
+
const fault = actor.crashing;
|
|
5086
|
+
this.closeValueAsync(value, this.faultText(fault), (closeFault) => {
|
|
5087
|
+
released();
|
|
5088
|
+
this.crashActor(actor, combineFaults(fault, closeFault), message.handler);
|
|
5089
|
+
message.complete(null, "Trapped");
|
|
5090
|
+
}, actor, actor.rcOwner);
|
|
5091
|
+
return;
|
|
5092
|
+
}
|
|
3847
5093
|
const drained = (fault) => {
|
|
5094
|
+
released();
|
|
3848
5095
|
actor.busy = false;
|
|
3849
5096
|
if (fault)
|
|
3850
5097
|
this.crashActor(actor, fault, message.handler);
|
|
@@ -3859,7 +5106,7 @@ class ExecutorV1 {
|
|
|
3859
5106
|
const shape = this.pkg.variants[message.handler.result_shape];
|
|
3860
5107
|
if (shape?.cases[value.tag]?.name === "Err") {
|
|
3861
5108
|
if (message.reply)
|
|
3862
|
-
message.complete(value.payload[0] ?? UNIT, "Failed", drained);
|
|
5109
|
+
message.complete(value.payload[0] ?? UNIT, "Failed", drained, value);
|
|
3863
5110
|
else {
|
|
3864
5111
|
this.crashActor(actor, {
|
|
3865
5112
|
kind: "panic",
|
|
@@ -3870,11 +5117,11 @@ class ExecutorV1 {
|
|
|
3870
5117
|
}
|
|
3871
5118
|
}
|
|
3872
5119
|
else {
|
|
3873
|
-
message.complete(value.payload[0] ?? UNIT, null, drained);
|
|
5120
|
+
message.complete(value.payload[0] ?? UNIT, null, drained, value);
|
|
3874
5121
|
}
|
|
3875
5122
|
}
|
|
3876
5123
|
else {
|
|
3877
|
-
message.complete(value, null, drained);
|
|
5124
|
+
message.complete(value, null, drained, value);
|
|
3878
5125
|
}
|
|
3879
5126
|
},
|
|
3880
5127
|
failed: (fault) => {
|
|
@@ -3950,6 +5197,8 @@ class ExecutorV1 {
|
|
|
3950
5197
|
this.reportFault(actor.closingFault);
|
|
3951
5198
|
}
|
|
3952
5199
|
actor.completed = true;
|
|
5200
|
+
actor.stateLease?.release();
|
|
5201
|
+
actor.stateLease = undefined;
|
|
3953
5202
|
for (const wake of actor.closed.splice(0))
|
|
3954
5203
|
wake();
|
|
3955
5204
|
for (const admit of actor.admission.splice(0))
|
|
@@ -3983,11 +5232,13 @@ class ExecutorV1 {
|
|
|
3983
5232
|
kind: "enum",
|
|
3984
5233
|
typeId: this.variantName(actor.layout.crash_kind),
|
|
3985
5234
|
shape: actor.layout.crash_kind,
|
|
3986
|
-
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code ===
|
|
3987
|
-
? "
|
|
3988
|
-
: code ===
|
|
3989
|
-
? "
|
|
3990
|
-
:
|
|
5235
|
+
tag: runtimeTag(this.pkg.variants[actor.layout.crash_kind], code === 200
|
|
5236
|
+
? "CrashKindHeapExceeded"
|
|
5237
|
+
: code === 214
|
|
5238
|
+
? "CrashKindTimerOverdue"
|
|
5239
|
+
: code === 213
|
|
5240
|
+
? "CrashKindHandlerFailed"
|
|
5241
|
+
: "CrashKindFault"),
|
|
3991
5242
|
payload: [],
|
|
3992
5243
|
},
|
|
3993
5244
|
stringValue(String(message)),
|
|
@@ -4372,6 +5623,10 @@ class ExecutorV1 {
|
|
|
4372
5623
|
}
|
|
4373
5624
|
drainMailbox(actor, fault, done) {
|
|
4374
5625
|
const messages = actor.mailbox.splice(0);
|
|
5626
|
+
const leases = [
|
|
5627
|
+
...actor.disposalLeases.splice(0),
|
|
5628
|
+
...messages.flatMap((message) => message.managedLease ? [message.managedLease] : []),
|
|
5629
|
+
];
|
|
4375
5630
|
actor.drainingMailbox = true;
|
|
4376
5631
|
this.closeValueAsync({
|
|
4377
5632
|
kind: "vector",
|
|
@@ -4385,6 +5640,7 @@ class ExecutorV1 {
|
|
|
4385
5640
|
})),
|
|
4386
5641
|
],
|
|
4387
5642
|
}, fault, (closeFault) => {
|
|
5643
|
+
leases.forEach((lease) => lease.release());
|
|
4388
5644
|
actor.drainingMailbox = false;
|
|
4389
5645
|
for (const message of messages)
|
|
4390
5646
|
message.complete(null, "Dead");
|
|
@@ -4464,6 +5720,8 @@ class ExecutorV1 {
|
|
|
4464
5720
|
}
|
|
4465
5721
|
actor.completed = true;
|
|
4466
5722
|
actor.alive = false;
|
|
5723
|
+
actor.stateLease?.release();
|
|
5724
|
+
actor.stateLease = undefined;
|
|
4467
5725
|
actor.busy = false;
|
|
4468
5726
|
this.trace.snapshot(actor.terminating ? "actor.terminate" : "actor.stop", { actor_id: actor.id });
|
|
4469
5727
|
this.settleSupervisedEnd(actor, actor.closingFault
|
|
@@ -4576,6 +5834,7 @@ function faultInfo(fault) {
|
|
|
4576
5834
|
shift_out_of_range: [204, "ShiftOutOfRange"],
|
|
4577
5835
|
vector_bounds: [205, "IndexOutOfBounds"],
|
|
4578
5836
|
actor_refused: [-4, "Refused"],
|
|
5837
|
+
heap_exceeded: [200, "HeapExceeded"],
|
|
4579
5838
|
}[fault.trap];
|
|
4580
5839
|
}
|
|
4581
5840
|
function faultApplicationMessage(fault) {
|
|
@@ -4606,12 +5865,30 @@ function faultReport(fault, secondary = false) {
|
|
|
4606
5865
|
(fault.secondary ?? []).map((next) => faultReport(next, true)).join(""));
|
|
4607
5866
|
}
|
|
4608
5867
|
// ── refs ────────────────────────────────────────────────────────────────────
|
|
4609
|
-
function ownedRef(value) {
|
|
4610
|
-
|
|
5868
|
+
function ownedRef(value, budget, type) {
|
|
5869
|
+
if (budget && type == null)
|
|
5870
|
+
throw new Error("owned value has no managed descriptor");
|
|
5871
|
+
return {
|
|
5872
|
+
kind: "cell",
|
|
5873
|
+
cell: {
|
|
5874
|
+
value,
|
|
5875
|
+
valid: true,
|
|
5876
|
+
budget,
|
|
5877
|
+
type,
|
|
5878
|
+
lease: budget?.retain({ value, type: type }),
|
|
5879
|
+
},
|
|
5880
|
+
};
|
|
4611
5881
|
}
|
|
4612
5882
|
function writeRef(ref, value) {
|
|
4613
5883
|
switch (ref.kind) {
|
|
4614
5884
|
case "cell":
|
|
5885
|
+
if (ref.cell.budget) {
|
|
5886
|
+
if (ref.cell.type == null)
|
|
5887
|
+
throw new Error("owned place has no managed descriptor");
|
|
5888
|
+
const lease = ref.cell.budget.retain({ value, type: ref.cell.type });
|
|
5889
|
+
ref.cell.lease?.release();
|
|
5890
|
+
ref.cell.lease = lease;
|
|
5891
|
+
}
|
|
4615
5892
|
ref.cell.value = value;
|
|
4616
5893
|
ref.cell.valid = true;
|
|
4617
5894
|
return;
|
|
@@ -4626,8 +5903,11 @@ function writeRef(ref, value) {
|
|
|
4626
5903
|
/// Only a cell of its own can be ended; a projection lives as long as the
|
|
4627
5904
|
/// storage it reaches through.
|
|
4628
5905
|
function invalidateRef(ref) {
|
|
4629
|
-
if (ref.kind === "cell")
|
|
5906
|
+
if (ref.kind === "cell") {
|
|
4630
5907
|
ref.cell.valid = false;
|
|
5908
|
+
ref.cell.lease?.release();
|
|
5909
|
+
ref.cell.lease = undefined;
|
|
5910
|
+
}
|
|
4631
5911
|
else
|
|
4632
5912
|
writeRef(ref, UNIT);
|
|
4633
5913
|
}
|
|
@@ -4734,11 +6014,7 @@ function truth(value) {
|
|
|
4734
6014
|
: value.kind === "i64" && value.value !== 0n;
|
|
4735
6015
|
}
|
|
4736
6016
|
function bytesValue(bytes) {
|
|
4737
|
-
return {
|
|
4738
|
-
kind: "vector",
|
|
4739
|
-
elementType: "u8",
|
|
4740
|
-
items: bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })),
|
|
4741
|
-
};
|
|
6017
|
+
return byteVectorValue(bytes.map((byte) => ({ kind: "i64", value: BigInt(byte) })));
|
|
4742
6018
|
}
|
|
4743
6019
|
function renderMessage(value) {
|
|
4744
6020
|
return value.kind === "string" ? value.value : canonical(value);
|
|
@@ -4804,6 +6080,8 @@ function trapMessage(trap) {
|
|
|
4804
6080
|
return "index out of bounds";
|
|
4805
6081
|
case "actor_refused":
|
|
4806
6082
|
return "delivery refused by interceptor";
|
|
6083
|
+
case "heap_exceeded":
|
|
6084
|
+
return "actor heap limit exceeded";
|
|
4807
6085
|
}
|
|
4808
6086
|
}
|
|
4809
6087
|
/// The operand type the package records. `isize` and `usize` are 64-bit in the
|