1+ import { Fail } from '@endo/errors' ;
2+
13import { getBaseMethods } from './base.ts' ;
4+ import { getCListMethods } from './clist.ts' ;
25import { getObjectMethods } from './object.ts' ;
6+ import { getPromiseMethods } from './promise.ts' ;
7+ import { getReachableMethods } from './reachable.ts' ;
38import { getRefCountMethods } from './refcount.ts' ;
9+ import { getVatMethods } from './vat.ts' ;
410import type {
511 VatId ,
6- EndpointId ,
712 KRef ,
813 GCAction ,
914 RunQueueItemBringOutYourDead ,
@@ -14,12 +19,7 @@ import {
1419 RunQueueItemType ,
1520} from '../../types.ts' ;
1621import type { StoreContext } from '../types.ts' ;
17- import { insistKernelType } from '../utils/kernel-slots.ts' ;
18- import { parseRef } from '../utils/parse-ref.ts' ;
19- import {
20- buildReachableAndVatSlot ,
21- parseReachableAndVatSlot ,
22- } from '../utils/reachable.ts' ;
22+ import { insistKernelType , parseKernelSlot } from '../utils/kernel-slots.ts' ;
2323
2424/**
2525 * Create a store for garbage collection.
@@ -30,9 +30,12 @@ import {
3030// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
3131export function getGCMethods ( ctx : StoreContext ) {
3232 const { getSlotKey } = getBaseMethods ( ctx . kv ) ;
33- const { getObjectRefCount, setObjectRefCount } = getObjectMethods ( ctx ) ;
34- const { kernelRefExists } = getRefCountMethods ( ctx ) ;
35-
33+ const { getRefCount } = getRefCountMethods ( ctx ) ;
34+ const { getObjectRefCount, deleteKernelObject } = getObjectMethods ( ctx ) ;
35+ const { getKernelPromise, deleteKernelPromise } = getPromiseMethods ( ctx ) ;
36+ const { decrementRefCount } = getCListMethods ( ctx ) ;
37+ const { getImporters } = getVatMethods ( ctx ) ;
38+ const { getReachableFlag, getReachableAndVatSlot } = getReachableMethods ( ctx ) ;
3639 /**
3740 * Get the set of GC actions to perform.
3841 *
@@ -71,49 +74,6 @@ export function getGCMethods(ctx: StoreContext) {
7174 setGCActions ( actions ) ;
7275 }
7376
74- /**
75- * Check if a kernel object is reachable.
76- *
77- * @param endpointId - The endpoint for which the reachable flag is being checked.
78- * @param kref - The kref.
79- * @returns True if the kernel object is reachable, false otherwise.
80- */
81- function getReachableFlag ( endpointId : EndpointId , kref : KRef ) : boolean {
82- const key = getSlotKey ( endpointId , kref ) ;
83- const data = ctx . kv . getRequired ( key ) ;
84- const { isReachable } = parseReachableAndVatSlot ( data ) ;
85- return isReachable ;
86- }
87-
88- /**
89- * Clear the reachable flag for a given endpoint and kref.
90- *
91- * @param endpointId - The endpoint for which the reachable flag is being cleared.
92- * @param kref - The kref.
93- */
94- function clearReachableFlag ( endpointId : EndpointId , kref : KRef ) : void {
95- const key = getSlotKey ( endpointId , kref ) ;
96- const { isReachable, vatSlot } = parseReachableAndVatSlot (
97- ctx . kv . getRequired ( key ) ,
98- ) ;
99- ctx . kv . set ( key , buildReachableAndVatSlot ( false , vatSlot ) ) ;
100- const { direction, isPromise } = parseRef ( vatSlot ) ;
101- // decrement 'reachable' part of refcount, but only for object imports
102- if (
103- isReachable &&
104- ! isPromise &&
105- direction === 'import' &&
106- kernelRefExists ( kref )
107- ) {
108- const counts = getObjectRefCount ( kref ) ;
109- counts . reachable -= 1 ;
110- setObjectRefCount ( kref , counts ) ;
111- if ( counts . reachable === 0 ) {
112- ctx . maybeFreeKrefs . add ( kref ) ;
113- }
114- }
115- }
116-
11777 /**
11878 * Schedule a vat for reaping.
11979 *
@@ -142,16 +102,124 @@ export function getGCMethods(ctx: StoreContext) {
142102 return undefined ;
143103 }
144104
105+ /**
106+ * Retires kernel objects by notifying importers and removing the objects.
107+ *
108+ * @param koids - Array of kernel object IDs to retire.
109+ */
110+ function retireKernelObjects ( koids : KRef [ ] ) : void {
111+ Array . isArray ( koids ) || Fail `retireExports given non-Array ${ koids } ` ;
112+ const newActions : GCAction [ ] = [ ] ;
113+ for ( const koid of koids ) {
114+ const importers = getImporters ( koid ) ;
115+ for ( const vatID of importers ) {
116+ newActions . push ( `${ vatID } retireImport ${ koid } ` ) ;
117+ }
118+ deleteKernelObject ( koid ) ;
119+ }
120+ addGCActions ( newActions ) ;
121+ }
122+
123+ /**
124+ * Processes reference counts for kernel resources and performs garbage collection actions
125+ * for resources that are no longer referenced or should be retired.
126+ */
127+ function collectGarbage ( ) : void {
128+ const actions : Set < GCAction > = new Set ( ) ;
129+ for ( const kref of ctx . maybeFreeKrefs . values ( ) ) {
130+ const { type } = parseKernelSlot ( kref ) ;
131+ if ( type === 'promise' ) {
132+ const kpid = kref ;
133+ const kp = getKernelPromise ( kpid ) ;
134+ const refCount = getRefCount ( kpid ) ;
135+ if ( refCount === 0 ) {
136+ if ( kp . state === 'fulfilled' || kp . state === 'rejected' ) {
137+ // https://github.com/Agoric/agoric-sdk/issues/9888 don't assume promise is settled
138+ for ( const slot of kp . value ?. slots ?? [ ] ) {
139+ // Note: the following decrement can result in an addition to the
140+ // maybeFreeKrefs set, which we are in the midst of iterating.
141+ // TC39 went to a lot of trouble to ensure that this is kosher.
142+ decrementRefCount ( slot ) ;
143+ }
144+ }
145+ deleteKernelPromise ( kpid ) ;
146+ }
147+ }
148+
149+ if ( type === 'object' ) {
150+ const { reachable, recognizable } = getObjectRefCount ( kref ) ;
151+ if ( reachable === 0 ) {
152+ // We avoid ownerOfKernelObject(), which will report
153+ // 'undefined' if the owner is dead (and being slowly
154+ // deleted). Message delivery should use that, but not us.
155+ const ownerKey = `${ kref } .owner` ;
156+ let ownerVatID = ctx . kv . get ( ownerKey ) ;
157+ const terminated = ctx . terminatedVats . includes ( ownerVatID as VatId ) ;
158+
159+ // Some objects that are still owned, but the owning vat
160+ // might still alive, or might be terminated and in the
161+ // process of being deleted. These two clauses are
162+ // mutually exclusive.
163+ if ( ownerVatID && ! terminated ) {
164+ const vatConsidersReachable = getReachableFlag ( ownerVatID , kref ) ;
165+ if ( vatConsidersReachable ) {
166+ // the reachable count is zero, but the vat doesn't realize it
167+ actions . add ( `${ ownerVatID } dropExport ${ kref } ` ) ;
168+ }
169+ if ( recognizable === 0 ) {
170+ // TODO: rethink this assert
171+ // assert.equal(vatConsidersReachable, false, `${kref} is reachable but not recognizable`);
172+ actions . add ( `${ ownerVatID } retireExport ${ kref } ` ) ;
173+ }
174+ } else if ( ownerVatID && terminated ) {
175+ // When we're slowly deleting a vat, and one of its
176+ // exports becomes unreferenced, we obviously must not
177+ // send dropExports or retireExports into the dead vat.
178+ // We fast-forward the abandonment that slow-deletion
179+ // would have done, then treat the object as orphaned.
180+
181+ const { vatSlot } = getReachableAndVatSlot ( ownerVatID , kref ) ;
182+ // delete directly, not orphanKernelObject(), which
183+ // would re-submit to maybeFreeKrefs
184+ ctx . kv . delete ( ownerKey ) ;
185+ ctx . kv . delete ( getSlotKey ( ownerVatID , kref ) ) ;
186+ ctx . kv . delete ( getSlotKey ( ownerVatID , vatSlot ) ) ;
187+ // now fall through to the orphaned case
188+ ownerVatID = undefined ;
189+ }
190+
191+ // Now handle objects which were orphaned. NOTE: this
192+ // includes objects which were owned by a terminated (but
193+ // not fully deleted) vat, where `ownerVatID` was cleared
194+ // in the last line of that previous clause (the
195+ // fall-through case). Don't try to change this `if
196+ // (!ownerVatID)` into an `else if`: the two clauses are
197+ // *not* mutually-exclusive.
198+ if ( ! ownerVatID ) {
199+ // orphaned and unreachable, so retire it. If the kref
200+ // is recognizable, then we need retireKernelObjects()
201+ // to scan for importers and send retireImports (and
202+ // delete), else we can call deleteKernelObject directly
203+ if ( recognizable ) {
204+ retireKernelObjects ( [ kref ] ) ;
205+ } else {
206+ deleteKernelObject ( kref ) ;
207+ }
208+ }
209+ }
210+ }
211+ }
212+ addGCActions ( [ ...actions ] ) ;
213+ ctx . maybeFreeKrefs . clear ( ) ;
214+ }
215+
145216 return {
146- // GC actions
147217 getGCActions,
148218 setGCActions,
149219 addGCActions,
150- // Reachability tracking
151- getReachableFlag,
152- clearReachableFlag,
153- // Reaping
154220 scheduleReap,
155221 nextReapAction,
222+ retireKernelObjects,
223+ collectGarbage,
156224 } ;
157225}
0 commit comments