Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions packages/kernel-test/src/garbage-collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ describe('Garbage Collection', () => {
importerVatId = vats.find(
(rows) => rows.config.parameters?.name === 'Importer',
)?.id as VatId;
exporterKRef = kernelStore.erefToKref(exporterVatId, 'o+0') as KRef;
importerKRef = kernelStore.erefToKref(importerVatId, 'o+0') as KRef;
exporterKRef = kernelStore.getRootObject(exporterVatId) as KRef;
importerKRef = kernelStore.getRootObject(importerVatId) as KRef;
});

it('objects are tracked with reference counts', async () => {
Expand Down
28 changes: 28 additions & 0 deletions packages/kernel/src/Kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,34 @@ export class Kernel {
}
}

/**
* Pin a vat root.
*
* @param vatId - The ID of the vat.
* @returns The KRef of the vat root.
*/
pinVatRoot(vatId: VatId): KRef {
const kref = this.#kernelStore.getRootObject(vatId);
if (!kref) {
throw new VatNotFoundError(vatId);
}
this.#kernelStore.pinObject(kref);
return kref;
}

/**
* Unpin a vat root.
*
* @param vatId - The ID of the vat.
*/
unpinVatRoot(vatId: VatId): void {
const kref = this.#kernelStore.getRootObject(vatId);
if (!kref) {
throw new VatNotFoundError(vatId);
}
this.#kernelStore.unpinObject(kref);
}

/**
* Reset the kernel state.
* This is for debugging purposes only.
Expand Down
49 changes: 30 additions & 19 deletions packages/kernel/src/store/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import type { Message } from '@agoric/swingset-liveslots';
import type { KernelDatabase } from '@ocap/store';
import { describe, it, expect, beforeEach } from 'vitest';

import { makeKernelStore } from './index.ts';
import { makeMapKernelDatabase } from '../../test/storage.ts';
import type { RunQueueItem } from '../types.ts';

/**
* Mock Message: A stupid TS hack to allow trivial use of plain strings as if they
* were Messages, since, for testing purposes here, all that's necessary to be a
* "message" is to be stringifiable.
*
* @param str - A string.
* @returns The same string coerced to type Message.
*/
function mm(str: string): Message {
return str as unknown as Message;
}

/**
* Mock RunQueueItem: A stupid TS hack to allow trivial use of plain strings
* as if they were RunQueueItems, since, for testing purposes here, all
Expand Down Expand Up @@ -89,11 +76,13 @@ describe('kernel store', () => {
'getNextVatId',
'getObjectRefCount',
'getOwner',
'getPinnedObjects',
'getPromisesByDecider',
'getQueueLength',
'getReachableAndVatSlot',
'getReachableFlag',
'getRefCount',
'getRootObject',
'getTerminatedVats',
'getVatConfig',
'getVatIDs',
Expand All @@ -104,6 +93,7 @@ describe('kernel store', () => {
'initEndpoint',
'initKernelObject',
'initKernelPromise',
'isObjectPinned',
'isRootObject',
'isVatTerminated',
'kernelRefExists',
Expand All @@ -114,7 +104,7 @@ describe('kernel store', () => {
'markVatAsTerminated',
'nextReapAction',
'nextTerminatedVatCleanup',
'refCountKey',
'pinObject',
'reset',
'resolveKernelPromise',
'retireKernelObjects',
Expand All @@ -128,6 +118,7 @@ describe('kernel store', () => {
'translateMessageKtoV',
'translateRefKtoV',
'translateSyscallVtoK',
'unpinObject',
]);
});
});
Expand Down Expand Up @@ -208,14 +199,34 @@ describe('kernel store', () => {
expect(ks.initKernelPromise()).toStrictEqual(['kp2', kp2]);
expect(ks.getKernelPromise('kp1')).toStrictEqual(kp1);
expect(ks.getKernelPromise('kp2')).toStrictEqual(kp2);
ks.enqueuePromiseMessage('kp1', mm('first message to kp1'));
ks.enqueuePromiseMessage('kp1', mm('second message to kp1'));
const msg1 = {
methargs: {
body: 'first message to kp1',
slots: [],
},
result: null,
};
ks.enqueuePromiseMessage('kp1', msg1);
const msg2 = {
methargs: {
body: 'second message to kp1',
slots: [],
},
result: null,
};
ks.enqueuePromiseMessage('kp1', msg2);
expect(ks.getKernelPromiseMessageQueue('kp1')).toStrictEqual([
'first message to kp1',
'second message to kp1',
msg1,
msg2,
]);
expect(ks.getKernelPromiseMessageQueue('kp1')).toStrictEqual([]);
ks.enqueuePromiseMessage('kp1', mm('sacrificial message'));
ks.enqueuePromiseMessage('kp1', {
methargs: {
body: 'sacrificial message',
slots: [],
},
result: null,
});
ks.deleteKernelPromise('kp1');
expect(() => ks.getKernelPromise('kp1')).toThrow(
'unknown kernel promise kp1',
Expand Down
4 changes: 4 additions & 0 deletions packages/kernel/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { getCListMethods } from './methods/clist.ts';
import { getGCMethods } from './methods/gc.ts';
import { getIdMethods } from './methods/id.ts';
import { getObjectMethods } from './methods/object.ts';
import { getPinMethods } from './methods/pinned.ts';
import { getPromiseMethods } from './methods/promise.ts';
import { getQueueMethods } from './methods/queue.ts';
import { getReachableMethods } from './methods/reachable.ts';
Expand Down Expand Up @@ -135,6 +136,8 @@ export function makeKernelStore(kdb: KernelDatabase) {
const vat = getVatMethods(context);
const reachable = getReachableMethods(context);
const translators = getTranslators(context);
const pinned = getPinMethods(context);

/**
* Create a new VatStore for a vat.
*
Expand Down Expand Up @@ -190,6 +193,7 @@ export function makeKernelStore(kdb: KernelDatabase) {
...cList,
...vat,
...translators,
...pinned,
makeVatStore,
deleteVat,
clear,
Expand Down
53 changes: 53 additions & 0 deletions packages/kernel/src/store/methods/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ describe('base-methods', () => {
});
});

describe('refCountKey', () => {
it('generates correct reference count keys', () => {
expect(baseStore.refCountKey('ko1')).toBe('ko1.refCount');
expect(baseStore.refCountKey('kp42')).toBe('kp42.refCount');
expect(baseStore.refCountKey('v7')).toBe('v7.refCount');
});
});

describe('getOwnerKey', () => {
it('generates correct owner keys', () => {
expect(baseStore.getOwnerKey('ko1')).toBe('ko1.owner');
expect(baseStore.getOwnerKey('kp42')).toBe('kp42.owner');
expect(baseStore.getOwnerKey('v7')).toBe('v7.owner');
});
});

describe('incCounter', () => {
it('increments a stored counter value', () => {
// Create a stored value to increment
Expand Down Expand Up @@ -159,6 +175,43 @@ describe('base-methods', () => {
});

describe('provideStoredQueue', () => {
it('creates a queue with cache when cached=true', () => {
const cachedQueue = baseStore.provideStoredQueue('cached-queue', true);
cachedQueue.enqueue({ id: 1 });
expect(kv.get('queue.cached-queue.head')).toBe('2');
cachedQueue.delete();
expect(kv.get('queue.cached-queue.head')).toBeUndefined();
});

it('creates a queue without cache when cached=false', () => {
const rawQueue = baseStore.provideStoredQueue('raw-queue', false);
rawQueue.enqueue({ id: 1 });
expect(kv.get('queue.raw-queue.head')).toBe('2');
rawQueue.delete();
expect(kv.get('queue.raw-queue.head')).toBeUndefined();
});

it('throws if queue is not initialized properly', () => {
kv.set('queue.broken-queue.head', '1');
const originalSet = kv.set;
vi.spyOn(kv, 'set').mockImplementation((key, value) => {
if (key !== 'queue.broken-queue.tail') {
return originalSet.call(kv, key, value);
}
return undefined;
});
expect(() => baseStore.provideStoredQueue('broken-queue')).toThrow(
'queue broken-queue not initialized',
);
});

it('returns undefined when dequeueing from a deleted queue', () => {
const queue = baseStore.provideStoredQueue('test-dequeue-deleted');
queue.enqueue({ id: 'test' });
kv.delete('queue.test-dequeue-deleted.head');
expect(queue.dequeue()).toBeUndefined();
});

it('throws when enqueueing into a deleted queue', () => {
// Create a queue properly
const queue = baseStore.provideStoredQueue('test', false);
Expand Down
22 changes: 22 additions & 0 deletions packages/kernel/src/store/methods/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ export function getBaseMethods(kv: KVStore) {
return `${endpointId}.c.${kref}`;
}

/**
* Generate the storage key for a kernel entity's reference count.
*
* @param kref - The KRef of interest.
* @returns the key to store the indicated reference count at.
*/
function refCountKey(kref: KRef): string {
return `${kref}.refCount`;
}

/**
* Generate the storage key for a kernel entity's owner.
*
* @param kref - The KRef of interest.
* @returns the key to store the indicated owner at.
*/
function getOwnerKey(kref: KRef): string {
return `${kref}.owner`;
}

/**
* Increment the value of a persistently stored counter.
*
Expand Down Expand Up @@ -176,6 +196,8 @@ export function getBaseMethods(kv: KVStore) {

return {
getSlotKey,
refCountKey,
getOwnerKey,
incCounter,
provideCachedStoredValue,
provideRawStoredValue,
Expand Down
Loading
Loading