Skip to content

Commit 06d73c3

Browse files
committed
Implement proper terminated vat cleanup
1 parent bdfe256 commit 06d73c3

File tree

5 files changed

+176
-9
lines changed

5 files changed

+176
-9
lines changed

packages/kernel/src/Kernel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export class Kernel {
181181
*/
182182
async #run(): Promise<void> {
183183
for await (const item of this.#runQueueItems()) {
184+
this.#kernelStore.nextTerminatedVatCleanup();
184185
await this.#deliver(item);
185186
this.#kernelStore.collectGarbage();
186187
}
@@ -832,6 +833,7 @@ export class Kernel {
832833
async terminateVat(vatId: VatId): Promise<void> {
833834
await this.#stopVat(vatId, true);
834835
this.#kernelStore.deleteVatConfig(vatId);
836+
this.#kernelStore.markVatAsTerminated(vatId);
835837
}
836838

837839
/**

packages/kernel/src/store/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ export function makeKernelStore(kdb: KernelDatabase) {
121121
// Garbage collection
122122
gcActions: provideCachedStoredValue('gcActions', '[]'),
123123
reapQueue: provideCachedStoredValue('reapQueue', '[]'),
124-
// TODO: Store terminated vats in DB and fetch from there
125-
terminatedVats: [],
124+
terminatedVats: provideCachedStoredValue('vats.terminated', '[]'),
126125
};
127126

128127
const id = getIdMethods(context);
@@ -152,7 +151,6 @@ export function makeKernelStore(kdb: KernelDatabase) {
152151
* @param vatId - The vat whose state is to be deleted.
153152
*/
154153
function deleteVat(vatId: VatId): void {
155-
vat.deleteEndpoint(vatId);
156154
vat.deleteVatConfig(vatId);
157155
kdb.deleteVatStore(vatId);
158156
}
@@ -163,10 +161,10 @@ export function makeKernelStore(kdb: KernelDatabase) {
163161
function reset(): void {
164162
kdb.clear();
165163
context.maybeFreeKrefs.clear();
166-
context.terminatedVats = [];
167164
context.runQueue = provideStoredQueue('run', true);
168165
context.gcActions = provideCachedStoredValue('gcActions', '[]');
169166
context.reapQueue = provideCachedStoredValue('reapQueue', '[]');
167+
context.terminatedVats = provideCachedStoredValue('vats.terminated', '[]');
170168
context.nextObjectId = provideCachedStoredValue('nextObjectId', '1');
171169
context.nextPromiseId = provideCachedStoredValue('nextPromiseId', '1');
172170
context.nextVatId = provideCachedStoredValue('nextVatId', '1');

packages/kernel/src/store/methods/gc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function getGCMethods(ctx: StoreContext) {
3434
const { getObjectRefCount, deleteKernelObject } = getObjectMethods(ctx);
3535
const { getKernelPromise, deleteKernelPromise } = getPromiseMethods(ctx);
3636
const { decrementRefCount } = getCListMethods(ctx);
37-
const { getImporters } = getVatMethods(ctx);
37+
const { getImporters, isVatTerminated } = getVatMethods(ctx);
3838
const { getReachableFlag, getReachableAndVatSlot } = getReachableMethods(ctx);
3939
/**
4040
* Get the set of GC actions to perform.
@@ -154,7 +154,7 @@ export function getGCMethods(ctx: StoreContext) {
154154
// deleted). Message delivery should use that, but not us.
155155
const ownerKey = `${kref}.owner`;
156156
let ownerVatID = ctx.kv.get(ownerKey);
157-
const terminated = ctx.terminatedVats.includes(ownerVatID as VatId);
157+
const terminated = isVatTerminated(ownerVatID as VatId);
158158

159159
// Some objects that are still owned, but the owning vat
160160
// might still alive, or might be terminated and in the

packages/kernel/src/store/methods/vat.ts

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getBaseMethods } from './base.ts';
2+
import { getCListMethods } from './clist.ts';
3+
import { getReachableMethods } from './reachable.ts';
24
import type { EndpointId, KRef, VatConfig, VatId } from '../../types.ts';
3-
import type { StoreContext } from '../types.ts';
5+
import type { StoreContext, VatCleanupWork } from '../types.ts';
46
import { parseRef } from '../utils/parse-ref.ts';
57
import { parseReachableAndVatSlot } from '../utils/reachable.ts';
68

@@ -22,6 +24,8 @@ const VAT_CONFIG_BASE_LEN = VAT_CONFIG_BASE.length;
2224
export function getVatMethods(ctx: StoreContext) {
2325
const { kv } = ctx;
2426
const { getPrefixedKeys, getSlotKey } = getBaseMethods(ctx.kv);
27+
const { decrementRefCount } = getCListMethods(ctx);
28+
const { clearReachableFlag } = getReachableMethods(ctx);
2529

2630
/**
2731
* Delete all persistent state associated with an endpoint.
@@ -129,6 +133,156 @@ export function getVatMethods(ctx: StoreContext) {
129133
return importers;
130134
}
131135

136+
/**
137+
* Get the list of terminated vats.
138+
*
139+
* @returns an array of terminated vat IDs.
140+
*/
141+
function getTerminatedVats(): VatId[] {
142+
return JSON.parse(ctx.terminatedVats.get() ?? '[]');
143+
}
144+
145+
/**
146+
* Check if a vat is terminated.
147+
*
148+
* @param vatID - The ID of the vat to check.
149+
* @returns True if the vat is terminated, false otherwise.
150+
*/
151+
function isVatTerminated(vatID: VatId): boolean {
152+
return getTerminatedVats().includes(vatID);
153+
}
154+
155+
/**
156+
* Add a vat to the list of terminated vats.
157+
*
158+
* @param vatID - The ID of the vat to add.
159+
*/
160+
function markVatAsTerminated(vatID: VatId): void {
161+
const terminatedVats = getTerminatedVats();
162+
if (!terminatedVats.includes(vatID)) {
163+
terminatedVats.push(vatID);
164+
ctx.terminatedVats.set(JSON.stringify(terminatedVats));
165+
}
166+
}
167+
168+
/**
169+
* Remove a vat from the list of terminated vats.
170+
*
171+
* @param vatID - The ID of the vat to remove.
172+
*/
173+
function forgetTerminatedVat(vatID: VatId): void {
174+
const terminatedVats = getTerminatedVats().filter((id) => id !== vatID);
175+
ctx.terminatedVats.set(JSON.stringify(terminatedVats));
176+
}
177+
178+
/**
179+
* Cleanup a terminated vat.
180+
*
181+
* @param vatID - The ID of the vat to cleanup.
182+
* @returns The work done during the cleanup.
183+
*/
184+
function cleanupTerminatedVat(vatID: VatId): VatCleanupWork {
185+
const work = {
186+
exports: 0,
187+
imports: 0,
188+
promises: 0,
189+
kv: 0,
190+
};
191+
192+
if (!isVatTerminated(vatID)) {
193+
return work;
194+
}
195+
196+
const clistPrefix = `${vatID}.c.`;
197+
const exportPrefix = `${clistPrefix}o+`;
198+
const importPrefix = `${clistPrefix}o-`;
199+
const promisePrefix = `${clistPrefix}p`;
200+
201+
// First, clean up exports (objects exported by the terminated vat)
202+
for (const key of getPrefixedKeys(exportPrefix)) {
203+
const vref = key.slice(clistPrefix.length);
204+
const kref = ctx.kv.get(key);
205+
if (kref) {
206+
const vatKey = getSlotKey(vatID, vref);
207+
const kernelKey = getSlotKey(vatID, kref);
208+
// Clear the reachable flag
209+
clearReachableFlag(vatID, kref);
210+
// Delete the c-list entries
211+
ctx.kv.delete(kernelKey);
212+
ctx.kv.delete(vatKey);
213+
// Delete the owner entry
214+
ctx.kv.delete(`${kref}.owner`);
215+
// Add to maybeFreeKrefs for GC processing
216+
ctx.maybeFreeKrefs.add(kref);
217+
work.exports += 1;
218+
}
219+
}
220+
221+
// Next, clean up imports (objects imported by the terminated vat)
222+
for (const key of getPrefixedKeys(importPrefix)) {
223+
const vref = key.slice(clistPrefix.length);
224+
const kref = ctx.kv.get(key);
225+
if (kref) {
226+
const vatKey = getSlotKey(vatID, vref);
227+
const kernelKey = getSlotKey(vatID, kref);
228+
// Clear the reachable flag
229+
clearReachableFlag(vatID, kref);
230+
// Decrement ref count for the import
231+
decrementRefCount(kref, {
232+
isExport: false,
233+
onlyRecognizable: true,
234+
});
235+
// Delete the c-list entries
236+
ctx.kv.delete(kernelKey);
237+
ctx.kv.delete(vatKey);
238+
work.imports += 1;
239+
}
240+
}
241+
242+
// Clean up promises
243+
for (const key of getPrefixedKeys(promisePrefix)) {
244+
const vref = key.slice(clistPrefix.length);
245+
const kref = ctx.kv.get(key);
246+
if (kref) {
247+
const vatKey = getSlotKey(vatID, vref);
248+
const kernelKey = getSlotKey(vatID, kref);
249+
// Decrement refcount for the promise
250+
decrementRefCount(kref);
251+
// Delete the c-list entries
252+
ctx.kv.delete(kernelKey);
253+
ctx.kv.delete(vatKey);
254+
work.promises += 1;
255+
}
256+
}
257+
258+
// Finally, clean up any remaining KV entries for this vat
259+
for (const key of getPrefixedKeys(`${vatID}.`)) {
260+
ctx.kv.delete(key);
261+
work.kv += 1;
262+
}
263+
264+
// Clean up any remaining c-list entries and vat-specific counters
265+
deleteEndpoint(vatID);
266+
267+
// Remove the vat from the terminated vats list
268+
forgetTerminatedVat(vatID);
269+
270+
// Log the cleanup work done
271+
console.log(`Cleaned up terminated vat ${vatID}:`, work);
272+
273+
return work;
274+
}
275+
276+
/**
277+
* Get the next terminated vat to cleanup.
278+
*
279+
* @returns The work done during the cleanup.
280+
*/
281+
function nextTerminatedVatCleanup(): VatCleanupWork | undefined {
282+
const vatID = getTerminatedVats()?.[0];
283+
return vatID ? cleanupTerminatedVat(vatID) : undefined;
284+
}
285+
132286
return {
133287
deleteEndpoint,
134288
getAllVatRecords,
@@ -138,5 +292,11 @@ export function getVatMethods(ctx: StoreContext) {
138292
getVatIDs,
139293
importsKernelSlot,
140294
getImporters,
295+
getTerminatedVats,
296+
markVatAsTerminated,
297+
forgetTerminatedVat,
298+
isVatTerminated,
299+
cleanupTerminatedVat,
300+
nextTerminatedVatCleanup,
141301
};
142302
}

packages/kernel/src/store/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { KVStore } from '@ocap/store';
22

3-
import type { KRef, VatId } from '../types.ts';
3+
import type { KRef } from '../types.ts';
44

55
export type StoreContext = {
66
kv: KVStore;
@@ -13,7 +13,7 @@ export type StoreContext = {
1313
maybeFreeKrefs: Set<KRef>;
1414
gcActions: StoredValue;
1515
reapQueue: StoredValue;
16-
terminatedVats: VatId[];
16+
terminatedVats: StoredValue;
1717
};
1818

1919
export type StoredValue = {
@@ -27,3 +27,10 @@ export type StoredQueue = {
2727
dequeue(): object | undefined;
2828
delete(): void;
2929
};
30+
31+
export type VatCleanupWork = {
32+
exports: number;
33+
imports: number;
34+
promises: number;
35+
kv: number;
36+
};

0 commit comments

Comments
 (0)