Skip to content

Commit 68fd6cf

Browse files
authored
refactor(kernel): Isolate Kernel Queue, Syscall, Router & Translation Components (#484)
This PR modularizes our kernel code for improved organization and maintainability by: - Creating a dedicated `KernelQueue` to manage the kernel’s run queue. - Refactored message routing logic from Kernel into dedicated `KernelRouter` class with specialized delivery handlers for each message type, improving separation of concerns and type safety. - Extracting the syscall handling logic into a new `VatSyscall` class for processing vat-related syscalls such as sending messages, resolving promises, and dropping imports. - Moving translation methods from both `Kernel` and `VatHandle` into the store object, centralizing the translation of references and messages between kernel and vat spaces.
1 parent 697008e commit 68fd6cf

23 files changed

+2139
-972
lines changed

packages/kernel/src/Kernel.ts

Lines changed: 53 additions & 600 deletions
Large diffs are not rendered by default.
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import type { Message, VatOneResolution } from '@agoric/swingset-liveslots';
2+
import type { CapData } from '@endo/marshal';
3+
import { makePromiseKit } from '@endo/promise-kit';
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
import type { MockInstance } from 'vitest';
6+
7+
import { KernelQueue } from './KernelQueue.ts';
8+
import type { KernelStore } from './store/index.ts';
9+
import type {
10+
KRef,
11+
RunQueueItem,
12+
RunQueueItemNotify,
13+
RunQueueItemSend,
14+
} from './types.ts';
15+
16+
vi.mock('./services/garbage-collection.ts', () => ({
17+
processGCActionSet: vi.fn().mockReturnValue(null),
18+
}));
19+
20+
vi.mock('@endo/promise-kit', () => ({
21+
makePromiseKit: vi.fn(),
22+
}));
23+
24+
describe('KernelQueue', () => {
25+
let kernelStore: KernelStore;
26+
let kernelQueue: KernelQueue;
27+
let mockPromiseKit: ReturnType<typeof makePromiseKit>;
28+
29+
beforeEach(() => {
30+
mockPromiseKit = {
31+
promise: Promise.resolve(),
32+
resolve: vi.fn(),
33+
reject: vi.fn(),
34+
};
35+
(makePromiseKit as unknown as MockInstance).mockReturnValue(mockPromiseKit);
36+
kernelStore = {
37+
nextTerminatedVatCleanup: vi.fn(),
38+
collectGarbage: vi.fn(),
39+
runQueueLength: vi.fn(),
40+
dequeueRun: vi.fn(),
41+
enqueueRun: vi.fn(),
42+
initKernelPromise: vi.fn().mockReturnValue(['kp1']),
43+
incrementRefCount: vi.fn(),
44+
getKernelPromise: vi.fn(),
45+
resolveKernelPromise: vi.fn(),
46+
nextReapAction: vi.fn().mockReturnValue(null),
47+
getGCActions: vi.fn().mockReturnValue([]),
48+
} as unknown as KernelStore;
49+
50+
kernelQueue = new KernelQueue(kernelStore);
51+
});
52+
53+
describe('run', () => {
54+
it('processes items from the run queue and performs cleanup', async () => {
55+
const mockItem: RunQueueItem = {
56+
type: 'send',
57+
target: 'ko123',
58+
message: {} as Message,
59+
};
60+
(
61+
kernelStore.runQueueLength as unknown as MockInstance
62+
).mockReturnValueOnce(1);
63+
(kernelStore.dequeueRun as unknown as MockInstance).mockReturnValue(
64+
mockItem,
65+
);
66+
const deliverError = new Error('stop');
67+
const deliver = vi.fn().mockRejectedValue(deliverError);
68+
await expect(kernelQueue.run(deliver)).rejects.toBe(deliverError);
69+
expect(kernelStore.nextTerminatedVatCleanup).toHaveBeenCalled();
70+
expect(deliver).toHaveBeenCalledWith(mockItem);
71+
});
72+
});
73+
74+
describe('enqueueMessage', () => {
75+
it('creates a message, enqueues it, and returns a promise for the result', async () => {
76+
const target = 'ko123';
77+
const method = 'test';
78+
const args = ['arg1', { key: 'value' }];
79+
const resultValue = { body: 'result', slots: [] };
80+
let resolvePromise = (_value: CapData<KRef>): void => {
81+
// do nothing
82+
};
83+
const resultPromiseRaw = new Promise<CapData<KRef>>((resolve) => {
84+
resolvePromise = resolve;
85+
});
86+
const successPromiseKit = {
87+
promise: resultPromiseRaw,
88+
resolve: resolvePromise,
89+
};
90+
(makePromiseKit as unknown as MockInstance).mockReturnValueOnce(
91+
successPromiseKit,
92+
);
93+
const resultPromise = kernelQueue.enqueueMessage(target, method, args);
94+
expect(kernelStore.initKernelPromise).toHaveBeenCalled();
95+
expect(kernelStore.incrementRefCount).toHaveBeenCalledWith(
96+
target,
97+
'queue|target',
98+
);
99+
expect(kernelStore.incrementRefCount).toHaveBeenCalledWith(
100+
'kp1',
101+
'queue|result',
102+
);
103+
expect(kernelStore.enqueueRun).toHaveBeenCalledWith({
104+
type: 'send',
105+
target,
106+
message: expect.objectContaining({
107+
methargs: expect.anything(),
108+
result: 'kp1',
109+
}),
110+
});
111+
expect(kernelQueue.subscriptions.has('kp1')).toBe(true);
112+
const handler = kernelQueue.subscriptions.get('kp1');
113+
expect(handler).toBeDefined();
114+
resolvePromise(resultValue);
115+
const result = await resultPromise;
116+
expect(result).toStrictEqual(resultValue);
117+
});
118+
});
119+
120+
describe('enqueueRun', () => {
121+
it('adds an item to the run queue', () => {
122+
const item: RunQueueItemSend = {
123+
type: 'send',
124+
target: 'ko123',
125+
message: {} as Message,
126+
};
127+
(kernelStore.runQueueLength as unknown as MockInstance).mockReturnValue(
128+
0,
129+
);
130+
kernelQueue.enqueueRun(item);
131+
expect(kernelStore.enqueueRun).toHaveBeenCalledWith(item);
132+
});
133+
});
134+
135+
describe('enqueueNotify', () => {
136+
it('creates a notify item and adds it to the run queue', () => {
137+
const vatId = 'v1';
138+
const kpid = 'kp123';
139+
kernelQueue.enqueueNotify(vatId, kpid);
140+
const expectedNotifyItem: RunQueueItemNotify = {
141+
type: 'notify',
142+
vatId,
143+
kpid,
144+
};
145+
expect(kernelStore.enqueueRun).toHaveBeenCalledWith(expectedNotifyItem);
146+
expect(kernelStore.incrementRefCount).toHaveBeenCalledWith(
147+
kpid,
148+
'notify',
149+
);
150+
});
151+
});
152+
153+
describe('resolvePromises', () => {
154+
it('resolves kernel promises and notifies subscribers', () => {
155+
const vatId = 'v1';
156+
const kpid = 'kp123';
157+
const resolution: VatOneResolution = [
158+
kpid,
159+
false,
160+
{ body: 'resolved value', slots: ['slot1'] } as CapData<KRef>,
161+
];
162+
(kernelStore.getKernelPromise as unknown as MockInstance).mockReturnValue(
163+
{
164+
state: 'unresolved',
165+
decider: vatId,
166+
subscribers: ['v2', 'v3'],
167+
},
168+
);
169+
const resolveHandler = vi.fn();
170+
kernelQueue.subscriptions.set(kpid, resolveHandler);
171+
kernelQueue.resolvePromises(vatId, [resolution]);
172+
expect(kernelStore.incrementRefCount).toHaveBeenCalledWith(
173+
kpid,
174+
'resolve|kpid',
175+
);
176+
expect(kernelStore.incrementRefCount).toHaveBeenCalledWith(
177+
'slot1',
178+
'resolve|slot',
179+
);
180+
expect(kernelStore.enqueueRun).toHaveBeenCalledWith({
181+
type: 'notify',
182+
vatId: 'v2',
183+
kpid,
184+
});
185+
expect(kernelStore.enqueueRun).toHaveBeenCalledWith({
186+
type: 'notify',
187+
vatId: 'v3',
188+
kpid,
189+
});
190+
expect(kernelStore.resolveKernelPromise).toHaveBeenCalledWith(
191+
kpid,
192+
false,
193+
{ body: 'resolved value', slots: ['slot1'] },
194+
);
195+
expect(resolveHandler).toHaveBeenCalledWith({
196+
body: 'resolved value',
197+
slots: ['slot1'],
198+
});
199+
expect(kernelQueue.subscriptions.has(kpid)).toBe(false);
200+
});
201+
202+
it('throws error if a promise is already resolved', () => {
203+
const vatId = 'v1';
204+
const kpid = 'kp123';
205+
const resolution: VatOneResolution = [
206+
kpid,
207+
false,
208+
{ body: 'resolved value', slots: [] } as CapData<KRef>,
209+
];
210+
(kernelStore.getKernelPromise as unknown as MockInstance).mockReturnValue(
211+
{
212+
state: 'fulfilled',
213+
decider: vatId,
214+
},
215+
);
216+
expect(() => kernelQueue.resolvePromises(vatId, [resolution])).toThrow(
217+
'"kp123" was already resolved',
218+
);
219+
});
220+
221+
it('throws error if the resolver is not the decider', () => {
222+
const vatId = 'v1';
223+
const wrongVatId = 'v2';
224+
const kpid = 'kp123';
225+
const resolution: VatOneResolution = [
226+
kpid,
227+
false,
228+
{ body: 'resolved value', slots: [] } as CapData<KRef>,
229+
];
230+
(kernelStore.getKernelPromise as unknown as MockInstance).mockReturnValue(
231+
{
232+
state: 'unresolved',
233+
decider: wrongVatId,
234+
},
235+
);
236+
expect(() => kernelQueue.resolvePromises(vatId, [resolution])).toThrow(
237+
'"v1" not permitted to resolve "kp123" because "its decider is v2"',
238+
);
239+
});
240+
});
241+
});

0 commit comments

Comments
 (0)