UNPKG

@metamask/ocap-kernel

Version:
205 lines 9.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getGCMethods = getGCMethods; const errors_1 = require("@endo/errors"); const base_ts_1 = require("./base.cjs"); const object_ts_1 = require("./object.cjs"); const promise_ts_1 = require("./promise.cjs"); const reachable_ts_1 = require("./reachable.cjs"); const refcount_ts_1 = require("./refcount.cjs"); const subclusters_ts_1 = require("./subclusters.cjs"); const vat_ts_1 = require("./vat.cjs"); const types_ts_1 = require("../../types.cjs"); const kernel_slots_ts_1 = require("../utils/kernel-slots.cjs"); /** * Create a store for garbage collection. * * @param ctx - The store context. * @returns The GC store. */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getGCMethods(ctx) { const { getSlotKey, getOwnerKey } = (0, base_ts_1.getBaseMethods)(ctx.kv); const { getRefCount, decrementRefCount } = (0, refcount_ts_1.getRefCountMethods)(ctx); const { getObjectRefCount, deleteKernelObject } = (0, object_ts_1.getObjectMethods)(ctx); const { getKernelPromise, deleteKernelPromise } = (0, promise_ts_1.getPromiseMethods)(ctx); const { getImporters, isVatTerminated } = (0, vat_ts_1.getVatMethods)(ctx); const { getReachableFlag, getReachableAndVatSlot } = (0, reachable_ts_1.getReachableMethods)(ctx); const { clearEmptySubclusters } = (0, subclusters_ts_1.getSubclusterMethods)(ctx); /** * Get the set of GC actions to perform. * * @returns The set of GC actions to perform. */ function getGCActions() { return new Set(JSON.parse(ctx.gcActions.get() ?? '[]')); } /** * Set the set of GC actions to perform. * * @param actions - The set of GC actions to perform. */ function setGCActions(actions) { const a = Array.from(actions); a.sort(); ctx.gcActions.set(JSON.stringify(a)); } /** * Add a new GC action to the set of GC actions to perform. * * @param newActions - The new GC action to add. */ function addGCActions(newActions) { const actions = getGCActions(); for (const action of newActions) { assert.typeof(action, 'string', 'addGCActions given bad action'); const [vatId, type, kref] = action.split(' '); (0, types_ts_1.insistVatId)(vatId); (0, types_ts_1.insistGCActionType)(type); (0, kernel_slots_ts_1.insistKernelType)('object', kref); actions.add(action); } setGCActions(actions); } /** * Schedule a vat for reaping. * * @param vatId - The vat to schedule for reaping. */ function scheduleReap(vatId) { const queue = JSON.parse(ctx.reapQueue.get() ?? '[]'); if (!queue.includes(vatId)) { queue.push(vatId); ctx.reapQueue.set(JSON.stringify(queue)); } } /** * Get the next reap action. * * @returns The next reap action, or undefined if the queue is empty. */ function nextReapAction() { const queue = JSON.parse(ctx.reapQueue.get() ?? '[]'); if (queue.length > 0) { const vatId = queue.shift(); ctx.reapQueue.set(JSON.stringify(queue)); return harden({ type: 'bringOutYourDead', vatId }); } return undefined; } /** * Retires kernel objects by notifying importers and removing the objects. * * @param koids - Array of kernel object IDs to retire. */ function retireKernelObjects(koids) { Array.isArray(koids) || (0, errors_1.Fail) `retireExports given non-Array ${koids}`; const newActions = []; for (const koid of koids) { const importers = getImporters(koid); for (const vatID of importers) { newActions.push(`${vatID} retireImport ${koid}`); } deleteKernelObject(koid); } addGCActions(newActions); } /** * Processes reference counts for kernel resources and performs garbage collection actions * for resources that are no longer referenced or should be retired. */ function collectGarbage() { const actions = new Set(); for (const kref of ctx.maybeFreeKrefs.values()) { const { type } = (0, kernel_slots_ts_1.parseKernelSlot)(kref); if (type === 'promise') { const kpid = kref; const kp = getKernelPromise(kpid); const refCount = getRefCount(kpid); if (refCount === 0) { if (kp.state === 'fulfilled' || kp.state === 'rejected') { // https://github.com/Agoric/agoric-sdk/issues/9888 don't assume promise is settled for (const slot of kp.value?.slots ?? []) { // Note: the following decrement can result in an addition to the // maybeFreeKrefs set, which we are in the midst of iterating. // TC39 went to a lot of trouble to ensure that this is kosher. decrementRefCount(slot, 'gc|promise|slot'); } } deleteKernelPromise(kpid); } } if (type === 'object') { const { reachable, recognizable } = getObjectRefCount(kref); if (reachable === 0) { const ownerKey = getOwnerKey(kref); let ownerVatID = ctx.kv.get(ownerKey); const terminated = isVatTerminated(ownerVatID); // Some objects that are still owned, but the owning vat // might still alive, or might be terminated and in the // process of being deleted. These two clauses are // mutually exclusive. if (ownerVatID && !terminated) { const vatConsidersReachable = getReachableFlag(ownerVatID, kref); if (vatConsidersReachable) { // the reachable count is zero, but the vat doesn't realize it actions.add(`${ownerVatID} dropExport ${kref}`); } if (recognizable === 0) { // TODO: rethink this assert // assert.equal(vatConsidersReachable, false, `${kref} is reachable but not recognizable`); actions.add(`${ownerVatID} retireExport ${kref}`); } } else if (ownerVatID && terminated) { // When we're slowly deleting a vat, and one of its // exports becomes unreferenced, we obviously must not // send dropExports or retireExports into the dead vat. // We fast-forward the abandonment that slow-deletion // would have done, then treat the object as orphaned. const { vatSlot } = getReachableAndVatSlot(ownerVatID, kref); // delete directly, not orphanKernelObject(), which // would re-submit to maybeFreeKrefs ctx.kv.delete(ownerKey); ctx.kv.delete(getSlotKey(ownerVatID, kref)); ctx.kv.delete(getSlotKey(ownerVatID, vatSlot)); // now fall through to the orphaned case ownerVatID = undefined; } // Now handle objects which were orphaned. NOTE: this // includes objects which were owned by a terminated (but // not fully deleted) vat, where `ownerVatID` was cleared // in the last line of that previous clause (the // fall-through case). Don't try to change this `if // (!ownerVatID)` into an `else if`: the two clauses are // *not* mutually-exclusive. if (!ownerVatID) { // orphaned and unreachable, so retire it. If the kref // is recognizable, then we need retireKernelObjects() // to scan for importers and send retireImports (and // delete), else we can call deleteKernelObject directly if (recognizable) { retireKernelObjects([kref]); } else { deleteKernelObject(kref); } } } } } addGCActions([...actions]); ctx.maybeFreeKrefs.clear(); clearEmptySubclusters(); } return { getGCActions, setGCActions, addGCActions, scheduleReap, nextReapAction, retireKernelObjects, collectGarbage, }; } //# sourceMappingURL=gc.cjs.map