Skip to content

Commit 202d24c

Browse files
committed
testing retireImports and dropImports
1 parent cc31d63 commit 202d24c

File tree

8 files changed

+196
-179
lines changed

8 files changed

+196
-179
lines changed

packages/kernel-test/src/garbage-collection.test.ts

Lines changed: 109 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { makeSQLKernelDatabase } from '@ocap/store/sqlite/nodejs';
66
import { waitUntilQuiescent } from '@ocap/utils';
77
import { expect, beforeEach, afterEach, describe, it } from 'vitest';
88

9-
import { getBundleSpec, makeKernel, runTestVats } from './utils.ts';
9+
import {
10+
getBundleSpec,
11+
makeKernel,
12+
parseReplyBody,
13+
runTestVats,
14+
} from './utils.ts';
1015

1116
/**
1217
* Make a test subcluster with vats for GC testing
@@ -103,7 +108,7 @@ describe('Garbage Collection E2E Tests', () => {
103108
[],
104109
);
105110
await waitUntilQuiescent();
106-
expect(useResult.body).toContain(objectId);
111+
expect(parseReplyBody(useResult.body)).toBe(objectId);
107112
});
108113

109114
it('should trigger GC syscalls through bringOutYourDead', async () => {
@@ -114,88 +119,91 @@ describe('Garbage Collection E2E Tests', () => {
114119
'createObject',
115120
[objectId],
116121
);
117-
const objectRef = createObjectData.slots[0] as KRef;
122+
const createObjectRef = createObjectData.slots[0] as KRef;
118123

119124
// Store initial reference count information
120-
const initialRefCounts = kernelStore.getObjectRefCount(objectRef);
125+
const initialRefCounts = kernelStore.getObjectRefCount(createObjectRef);
126+
console.log('Initial ref counts:', createObjectRef, initialRefCounts);
121127
expect(initialRefCounts.reachable).toBe(1);
122128
expect(initialRefCounts.recognizable).toBe(1);
123129

124130
// 2. Store the reference in the importer vat
131+
const objectRef = kunser(createObjectData);
125132
await kernel.queueMessageFromKernel(importerKRef, 'storeImport', [
126133
objectRef,
127134
objectId,
128135
]);
129136
await waitUntilQuiescent();
130137

138+
// Get reference counts after storing in importer
139+
const afterStoreRefCounts = kernelStore.getObjectRefCount(createObjectRef);
140+
console.log('After store ref counts:', objectRef, afterStoreRefCounts);
141+
131142
// 3. Verify object is tracked in both vats
132143
const importerHasObject = await kernel.queueMessageFromKernel(
133144
importerKRef,
134145
'listImportedObjects',
135146
[],
136147
);
137148
console.log('$$$ importerHasObject', importerHasObject);
138-
expect(importerHasObject.body).toContain(objectId);
149+
expect(parseReplyBody(importerHasObject.body)).toContain(objectId);
139150

140151
const exporterHasObject = await kernel.queueMessageFromKernel(
141152
exporterKRef,
142153
'isObjectPresent',
143154
[objectId],
144155
);
145156
console.log('$$$ exporterHasObject', exporterHasObject);
146-
expect(exporterHasObject.body).toBe('#true');
157+
expect(parseReplyBody(exporterHasObject.body)).toBe(true);
147158

148159
// 4. Make a weak reference to the object in the importer vat
149160
// This should eventually trigger dropImports when GC runs
150161
await kernel.queueMessageFromKernel(importerKRef, 'makeWeak', [objectId]);
151162
await waitUntilQuiescent();
152163

153-
// 5. Schedule reap to trigger bringOutYourDead on next crank
154-
kernelStore.scheduleReap(exporterVatId);
164+
console.log('$$$ kernelStore.getGCActions(1)', kernelStore.getGCActions());
165+
166+
// // 5. Schedule reap to trigger bringOutYourDead on next crank
167+
kernel.reapAllVats();
155168

156-
// 6. Run a crank to allow bringOutYourDead to be processed
169+
// // 6. Run a crank to allow bringOutYourDead to be processed
157170
await kernel.queueMessageFromKernel(exporterKRef, 'noop', []);
158171
await waitUntilQuiescent(100);
159172

160-
// Check reference counts after dropImports (should be decreased reachable but same recognizable)
161-
const afterWeakRefCounts = kernelStore.getObjectRefCount(objectRef);
162-
expect(afterWeakRefCounts.reachable).toBeLessThan(
163-
initialRefCounts.reachable,
164-
);
165-
expect(afterWeakRefCounts.recognizable).toBe(initialRefCounts.recognizable);
173+
console.log('$$$ kernelStore.getGCActions(2)', kernelStore.getGCActions());
174+
175+
// // Check reference counts after dropImports
176+
const afterWeakRefCounts = kernelStore.getObjectRefCount(createObjectRef);
177+
console.log('After weak ref counts:', afterWeakRefCounts);
178+
expect(afterWeakRefCounts.reachable).toBe(0);
179+
expect(afterWeakRefCounts.recognizable).toBe(1);
166180

167181
// 7. Now completely forget the import in the importer vat
168182
// This should trigger retireImports when GC runs
169-
await kernel.queueMessageFromKernel(importerKRef, 'forgetImport', [
170-
objectId,
171-
]);
183+
await kernel.queueMessageFromKernel(importerKRef, 'forgetImport', []);
172184
await waitUntilQuiescent();
173185

174186
// 8. Schedule another reap
175-
kernelStore.scheduleReap(importerVatId);
187+
kernel.reapAllVats();
176188

177189
// 9. Run a crank to allow bringOutYourDead to be processed
178190
await kernel.queueMessageFromKernel(importerKRef, 'noop', []);
179191
await waitUntilQuiescent(100);
180192

181193
// Check reference counts after retireImports (both should be decreased)
182-
const afterForgetRefCounts = kernelStore.getObjectRefCount(objectRef);
183-
expect(afterForgetRefCounts.reachable).toBeLessThan(
184-
initialRefCounts.reachable,
185-
);
186-
expect(afterForgetRefCounts.recognizable).toBeLessThan(
187-
initialRefCounts.recognizable,
188-
);
194+
const afterForgetRefCounts = kernelStore.getObjectRefCount(createObjectRef);
195+
expect(afterForgetRefCounts.reachable).toBe(0);
196+
expect(afterForgetRefCounts.recognizable).toBe(0);
189197

190-
// 10. Now forget the object in the exporter vat
191-
// This should trigger retireExports when GC runs
198+
// // 10. Now forget the object in the exporter vat
199+
// // This should trigger retireExports when GC runs
192200
await kernel.queueMessageFromKernel(exporterKRef, 'forgetObject', [
193201
objectId,
194202
]);
195203
await waitUntilQuiescent();
196204

197205
// 11. Schedule a final reap
198-
kernelStore.scheduleReap(exporterVatId);
206+
kernel.reapAllVats();
199207

200208
// 12. Run multiple cranks to ensure GC completes
201209
for (let i = 0; i < 3; i++) {
@@ -210,77 +218,79 @@ describe('Garbage Collection E2E Tests', () => {
210218
[objectId],
211219
);
212220
console.log('$$$ exporterFinalCheck', exporterFinalCheck);
213-
expect(exporterFinalCheck.body).toBe('#false');
221+
expect(parseReplyBody(exporterFinalCheck.body)).toBe(false);
214222

215223
// Check if reference still exists in the kernel store at all
216-
const refExists = kernelStore.kernelRefExists(objectRef);
217-
expect(refExists).toBe(false);
218-
219-
// 13. Test abandonExports by creating a new object and forcing its removal
220-
const abandonObjectId = 'abandon-test';
221-
const abandonObjData = await kernel.queueMessageFromKernel(
222-
exporterKRef,
223-
'createObject',
224-
[abandonObjectId],
225-
);
226-
console.log('$$$ abandonObjData', abandonObjData);
227-
const abandonObjRef = abandonObjData.slots[0] as KRef;
228-
229-
// Store in importer to make it reachable from both vats
230-
await kernel.queueMessageFromKernel(importerKRef, 'storeImport', [
231-
abandonObjRef,
232-
abandonObjectId,
233-
]);
234-
await waitUntilQuiescent();
235-
236-
// Verify it's reachable from both vats
237-
const abandonRefCounts = kernelStore.getObjectRefCount(abandonObjRef);
238-
expect(abandonRefCounts.reachable).toBe(1);
239-
240-
// Force remove in exporter (this simulates abandonExports)
241-
await kernel.queueMessageFromKernel(exporterKRef, 'forgetObject', [
242-
abandonObjectId,
243-
]);
244-
await waitUntilQuiescent();
245-
246-
// Schedule reap to trigger abandonExports
247-
kernelStore.scheduleReap(exporterVatId);
248-
249-
// Run multiple cranks to ensure GC completes
250-
for (let i = 0; i < 3; i++) {
251-
await kernel.queueMessageFromKernel(exporterKRef, 'noop', []);
252-
await waitUntilQuiescent(50);
253-
}
254-
255-
// Verify object is gone from exporter
256-
const exporterAbandonCheck = await kernel.queueMessageFromKernel(
257-
exporterKRef,
258-
'isObjectPresent',
259-
[abandonObjectId],
260-
);
261-
console.log('$$$ exporterAbandonCheck', exporterAbandonCheck);
262-
expect(exporterAbandonCheck.body).toBe('#false');
263-
264-
// But it should still be in the importer's list
265-
const importerAbandonCheck = await kernel.queueMessageFromKernel(
266-
importerKRef,
267-
'listImportedObjects',
268-
[],
269-
);
270-
console.log('$$$ importerAbandonCheck', importerAbandonCheck);
271-
expect(importerAbandonCheck.body).toContain(abandonObjectId);
272-
273-
// However, using the object should now fail
274-
try {
275-
await kernel.queueMessageFromKernel(importerKRef, 'useImport', [
276-
abandonObjectId,
277-
]);
278-
// Should not reach here
279-
expect(false).toBe(true);
280-
} catch (error) {
281-
// We expect an error
282-
// eslint-disable-next-line vitest/no-conditional-expect
283-
expect(error).toBeDefined();
284-
}
224+
// const refExists = kernelStore.kernelRefExists(createObjectRef);
225+
// expect(refExists).toBe(false);
226+
227+
// // 13. Test abandonExports by creating a new object and forcing its removal
228+
// const abandonObjectId = 'abandon-test';
229+
// const abandonObjData = await kernel.queueMessageFromKernel(
230+
// exporterKRef,
231+
// 'createObject',
232+
// [abandonObjectId],
233+
// );
234+
// console.log('$$$ abandonObjData', abandonObjData);
235+
// const abandonObjRef = abandonObjData.slots[0] as KRef;
236+
237+
// // Store in importer to make it reachable from both vats
238+
// await kernel.queueMessageFromKernel(importerKRef, 'storeImport', [
239+
// abandonObjRef,
240+
// abandonObjectId,
241+
// ]);
242+
// await waitUntilQuiescent();
243+
244+
// // Verify it's reachable from both vats
245+
// const abandonRefCounts = kernelStore.getObjectRefCount(abandonObjRef);
246+
// expect(abandonRefCounts.reachable).toBe(1);
247+
248+
// // Force remove in exporter (this simulates abandonExports)
249+
// await kernel.queueMessageFromKernel(exporterKRef, 'forgetObject', [
250+
// abandonObjectId,
251+
// ]);
252+
// await waitUntilQuiescent();
253+
254+
// // Schedule reap to trigger abandonExports
255+
// kernelStore.scheduleReap(exporterVatId);
256+
257+
// // Run multiple cranks to ensure GC completes
258+
// for (let i = 0; i < 3; i++) {
259+
// await kernel.queueMessageFromKernel(exporterKRef, 'noop', []);
260+
// await waitUntilQuiescent(50);
261+
// }
262+
263+
// // Verify object is gone from exporter
264+
// const exporterAbandonCheck = await kernel.queueMessageFromKernel(
265+
// exporterKRef,
266+
// 'isObjectPresent',
267+
// [abandonObjectId],
268+
// );
269+
// console.log('$$$ exporterAbandonCheck', exporterAbandonCheck);
270+
// expect(parseReplyBody(exporterAbandonCheck.body)).toBe(false);
271+
272+
// // But it should still be in the importer's list
273+
// const importerAbandonCheck = await kernel.queueMessageFromKernel(
274+
// importerKRef,
275+
// 'listImportedObjects',
276+
// [],
277+
// );
278+
// console.log('$$$ importerAbandonCheck', importerAbandonCheck);
279+
// expect(parseReplyBody(importerAbandonCheck.body)).toContain(
280+
// abandonObjectId,
281+
// );
282+
283+
// // However, using the object should now fail
284+
// try {
285+
// await kernel.queueMessageFromKernel(importerKRef, 'useImport', [
286+
// abandonObjectId,
287+
// ]);
288+
// // Should not reach here
289+
// expect(false).toBe(true);
290+
// } catch (error) {
291+
// // We expect an error
292+
// // eslint-disable-next-line vitest/no-conditional-expect
293+
// expect(error).toBeDefined();
294+
// }
285295
});
286296
});

packages/kernel-test/src/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,18 @@ export function extractVatLogs(buffer: string): string[] {
135135
.map((line: string) => line.slice(4));
136136
return sortLogs(result);
137137
}
138+
139+
/**
140+
* Parse a message body into a JSON object.
141+
*
142+
* @param body - The message body to parse.
143+
*
144+
* @returns The parsed JSON object, or the original body if parsing fails.
145+
*/
146+
export function parseReplyBody(body: string): unknown {
147+
try {
148+
return JSON.parse(body.slice(1));
149+
} catch {
150+
return body;
151+
}
152+
}

packages/kernel-test/src/vats/exporter-vat.js

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,25 @@ export function buildRootObject(_vatPowers, parameters, _baggage) {
6060
return obj;
6161
},
6262

63-
getExportedObjectCount() {
64-
return exportedObjects.size;
63+
// Check if an object exists in our maps
64+
isObjectPresent(objId) {
65+
return exportedObjects.has(objId);
66+
},
67+
68+
// Remove an object from our tracking, allowing it to be GC'd
69+
forgetObject(objId) {
70+
if (exportedObjects.has(objId)) {
71+
tlog(`Forgetting object ${objId}`);
72+
exportedObjects.delete(objId);
73+
return true;
74+
}
75+
tlog(`Cannot forget nonexistent object: ${objId}`);
76+
return false;
77+
},
78+
79+
// No-op to help trigger crank cycles
80+
noop() {
81+
return 'noop';
6582
},
6683

6784
// Create a weak reference to an object
@@ -111,26 +128,5 @@ export function buildRootObject(_vatPowers, parameters, _baggage) {
111128
return { status: 'error', message: String(error) };
112129
}
113130
},
114-
115-
// Check if an object exists in our maps
116-
isObjectPresent(objId) {
117-
return exportedObjects.has(objId);
118-
},
119-
120-
// Remove an object from our tracking, allowing it to be GC'd
121-
forgetObject(objId) {
122-
if (exportedObjects.has(objId)) {
123-
tlog(`Forgetting object ${objId}`);
124-
exportedObjects.delete(objId);
125-
return true;
126-
}
127-
tlog(`Cannot forget nonexistent object: ${objId}`);
128-
return false;
129-
},
130-
131-
// No-op to help trigger crank cycles
132-
noop() {
133-
return 'noop';
134-
},
135131
});
136132
}

0 commit comments

Comments
 (0)