Skip to content

Commit a855062

Browse files
committed
test(kernel-node): Add failing orphaned ephemeral exo test
1 parent a2a205d commit a855062

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { makeSQLKernelDatabase } from '@metamask/kernel-store/sqlite/nodejs';
2+
import { kunser } from '@metamask/ocap-kernel';
3+
import type { ClusterConfig } from '@metamask/ocap-kernel';
4+
import { delay } from '@ocap/repo-tools/test-utils';
5+
import { mkdtemp, rm } from 'node:fs/promises';
6+
import { tmpdir } from 'node:os';
7+
import { join } from 'node:path';
8+
import { describe, it, expect } from 'vitest';
9+
10+
import { makeTestKernel } from '../helpers/kernel.ts';
11+
12+
const PROVIDER_BUNDLE =
13+
'http://localhost:3000/orphaned-ephemeral-provider-vat.bundle';
14+
const CONSUMER_BUNDLE =
15+
'http://localhost:3000/orphaned-ephemeral-consumer-vat.bundle';
16+
17+
const clusterConfig: ClusterConfig = {
18+
bootstrap: 'consumer',
19+
vats: {
20+
provider: {
21+
bundleSpec: PROVIDER_BUNDLE,
22+
parameters: {},
23+
},
24+
consumer: {
25+
bundleSpec: CONSUMER_BUNDLE,
26+
parameters: {},
27+
},
28+
},
29+
};
30+
31+
describe('Orphaned ephemeral exo', { timeout: 30_000 }, () => {
32+
it('rejects when provider vat restarts', async () => {
33+
const tempDir = await mkdtemp(join(tmpdir(), 'ocap-ephemeral-'));
34+
const dbFilename = join(tempDir, 'kernel.db');
35+
try {
36+
const kernel = await makeTestKernel(
37+
await makeSQLKernelDatabase({ dbFilename }),
38+
);
39+
try {
40+
const { rootKref, subclusterId } =
41+
await kernel.launchSubcluster(clusterConfig);
42+
await delay();
43+
44+
// Works before restart
45+
const r1 = await kernel.queueMessage(rootKref, 'useEphemeral', []);
46+
expect(kunser(r1)).toBe(999);
47+
48+
// Restart only the provider — the consumer still holds the
49+
// ephemeral ref, but the exo behind it no longer exists.
50+
const subcluster = kernel.getSubcluster(subclusterId);
51+
expect(subcluster).toBeDefined();
52+
await kernel.restartVat(subcluster!.vats.provider);
53+
await delay();
54+
55+
// The consumer's E(ephemeral).increment() targets an orphaned vref.
56+
// Liveslots in the provider throws "I don't remember allocating",
57+
// which terminates the provider and rejects the caller's promise.
58+
await expect(
59+
kernel.queueMessage(rootKref, 'useEphemeral', []),
60+
).rejects.toMatchObject({
61+
body: expect.stringContaining("I don't remember allocating"),
62+
});
63+
} finally {
64+
await kernel.stop();
65+
}
66+
} finally {
67+
await rm(tempDir, { recursive: true, force: true });
68+
}
69+
});
70+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { E } from '@endo/eventual-send';
2+
import { makeDefaultExo } from '@metamask/kernel-utils/exo';
3+
4+
/**
5+
* A consumer vat that obtains an ephemeral exo reference from the provider
6+
* during bootstrap and calls it on demand.
7+
*
8+
* @returns The root object.
9+
*/
10+
export function buildRootObject() {
11+
let ephemeralRef: unknown;
12+
13+
return makeDefaultExo('root', {
14+
async bootstrap(vats: { provider: unknown }) {
15+
ephemeralRef = await E(vats.provider).getEphemeral();
16+
},
17+
18+
async useEphemeral() {
19+
return E(ephemeralRef).increment();
20+
},
21+
});
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { makeDefaultExo } from '@metamask/kernel-utils/exo';
2+
3+
/**
4+
* A provider vat that exposes a single ephemeral (non-durable) exo.
5+
* The exo will not survive a vat restart.
6+
*
7+
* @returns The root object.
8+
*/
9+
export function buildRootObject() {
10+
const ephemeral = makeDefaultExo('EphemeralCounter', {
11+
increment() {
12+
return 999;
13+
},
14+
});
15+
16+
return makeDefaultExo('root', {
17+
getEphemeral() {
18+
return ephemeral;
19+
},
20+
});
21+
}

0 commit comments

Comments
 (0)