Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ocap-kernel/src/store/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ describe('kernel store', () => {
'initKernelObject',
'initKernelPromise',
'invertRRef',
'isInCrank',
'isObjectPinned',
'isRevoked',
'isRootObject',
Expand Down
13 changes: 13 additions & 0 deletions packages/ocap-kernel/src/store/methods/crank.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,17 @@ describe('crank methods', () => {
crankMethods.endCrank();
});
});

describe('isInCrank', () => {
it('returns false when not in a crank', () => {
expect(crankMethods.isInCrank()).toBe(false);
});

it('returns true during a crank', () => {
crankMethods.startCrank();
expect(crankMethods.isInCrank()).toBe(true);
crankMethods.endCrank();
expect(crankMethods.isInCrank()).toBe(false);
});
});
});
10 changes: 10 additions & 0 deletions packages/ocap-kernel/src/store/methods/crank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ export function getCrankMethods(ctx: StoreContext, kdb: KernelDatabase) {
return items;
}

/**
* Check whether the kernel is currently inside a crank.
*
* @returns True if a crank is in progress.
*/
function isInCrank(): boolean {
return ctx.inCrank;
}

return {
startCrank,
createCrankSavepoint,
Expand All @@ -115,5 +124,6 @@ export function getCrankMethods(ctx: StoreContext, kdb: KernelDatabase) {
waitForCrank,
bufferCrankOutput,
flushCrankBuffer,
isInCrank,
};
}
34 changes: 34 additions & 0 deletions packages/ocap-kernel/src/vats/VatSyscall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('VatSyscall', () => {
forgetKref: vi.fn(),
getVatConfig: vi.fn(() => ({})),
isVatActive: vi.fn(() => true),
isInCrank: vi.fn(() => true),
} as unknown as KernelStore;
logger = {
debug: vi.fn(),
Expand Down Expand Up @@ -66,6 +67,27 @@ describe('VatSyscall', () => {
);
});

it('enqueues send immediately when outside a crank', () => {
(kernelStore.isInCrank as unknown as MockInstance).mockReturnValue(false);
const target = 'o+1';
const message = { methargs: { body: '', slots: [] } } as unknown as Message;
const vso = ['send', target, message] as unknown as VatSyscallObject;
vatSys.handleSyscall(vso);
expect(kernelQueue.enqueueSend).toHaveBeenCalledWith(target, message, true);
});

it('resolves promises immediately when outside a crank', () => {
(kernelStore.isInCrank as unknown as MockInstance).mockReturnValue(false);
const resolution = ['kp1', false, {}] as unknown as VatOneResolution;
const vso = ['resolve', [resolution]] as unknown as VatSyscallObject;
vatSys.handleSyscall(vso);
expect(kernelQueue.resolvePromises).toHaveBeenCalledWith(
'v1',
[resolution],
true,
);
});

describe('subscribe syscall', () => {
it('subscribes to unresolved promise', async () => {
(
Expand Down Expand Up @@ -95,6 +117,18 @@ describe('VatSyscall', () => {
false,
);
});

it('notifies immediately for resolved promise when outside a crank', () => {
(kernelStore.isInCrank as unknown as MockInstance).mockReturnValue(false);
(
kernelStore.getKernelPromise as unknown as MockInstance
).mockReturnValueOnce({
state: 'fulfilled',
});
const vso = ['subscribe', 'kp1'] as unknown as VatSyscallObject;
vatSys.handleSyscall(vso);
expect(kernelQueue.enqueueNotify).toHaveBeenCalledWith('v1', 'kp1', true);
});
});

describe('dropImports syscall', () => {
Expand Down
20 changes: 14 additions & 6 deletions packages/ocap-kernel/src/vats/VatSyscall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,30 @@ export class VatSyscall {
}

/**
* Handle a 'send' syscall from the vat. The send is buffered and will be
* flushed to the run queue on successful crank completion.
* Handle a 'send' syscall from the vat. During a crank, the send is
* buffered and flushed on crank completion. Outside a crank (async vat
* operations like fetch), the send is enqueued immediately to wake the
* run queue.
*
* @param target - The target of the message send.
* @param message - The message that was sent.
*/
#handleSyscallSend(target: KRef, message: Message): void {
this.#kernelQueue.enqueueSend(target, message, false);
const immediate = !this.#kernelStore.isInCrank();
this.#kernelQueue.enqueueSend(target, message, immediate);
}

/**
* Handle a 'resolve' syscall from the vat.
* Handle a 'resolve' syscall from the vat. During a crank, notifications
* are buffered and flushed on crank completion. Outside a crank (async vat
* operations like fetch), notifications are enqueued immediately to wake
* the run queue.
*
* @param resolutions - One or more promise resolutions.
*/
#handleSyscallResolve(resolutions: VatOneResolution[]): void {
this.#kernelQueue.resolvePromises(this.vatId, resolutions, false);
const immediate = !this.#kernelStore.isInCrank();
this.#kernelQueue.resolvePromises(this.vatId, resolutions, immediate);
}

/**
Expand All @@ -100,7 +107,8 @@ export class VatSyscall {
if (kp.state === 'unresolved') {
this.#kernelStore.addPromiseSubscriber(this.vatId, kpid);
} else {
this.#kernelQueue.enqueueNotify(this.vatId, kpid, false);
const immediate = !this.#kernelStore.isInCrank();
this.#kernelQueue.enqueueNotify(this.vatId, kpid, immediate);
}
}

Expand Down
Loading