Skip to content

retireKernelObjects never notifies remote importers, leaving a dangling c-list entry #1015

Description

@grypez

Raising this as unconfirmed — I believe it is a bug, but I have not verified it against a running remote link, and there is a plausible reading in which it is by design. Evidence and the counter-case both below so it can be judged independently.

Observed

With a real kernel store: an object exported by v1, imported by both a vat (v2) and a remote (r1), dropped by both, then orphaned by its owner. After collectGarbage():

getGCActions()  -> [ 'v2 retireImport ko1' ]

auditRefCounts() -> [ { kind: 'dangling',
                        kref: 'ko1',
                        expected: '0,1',
                        holders: [ 'r1 c-list import ro-1' ] } ]

The vat importer gets a retireImport. The remote importer gets nothing, and the audit reports its c-list entry as a dangling reference — on that crank and on every crank after.

Mechanism

retireKernelObjects (store/methods/gc.ts:138-149) builds its actions from getImporters(koid). getImporters filters getVatIDs() (store/methods/vat.ts:150-157), which is derived from the vatConfig. key prefix — so it enumerates vats only, never remotes. deleteKernelObject is called in the same loop.

The remote's import c-list entry therefore outlives the object it names, with no action scheduled to tear it down. Since #1010 that entry also holds a recognizable count (store/methods/clist.ts:50), which is why the audit now has something to disagree about.

Why I think it matters

Two consequences at different confidence levels:

  • High confidence: the c-list entry and its counts leak. Nothing will ever remove them.
  • Medium confidence: any future test that retires an object a remote still recognises will fail auditRefCounts, and kernel-test enables auditing on every kernel it builds (packages/kernel-test/src/utils.ts:98).

Why this might not be an issue

  • It is latent today. Reproducing it took an explicit initEndpoint('r1') and a hand-driven drop/orphan sequence. Nothing in kernel-test drives that, which is why CI is green — fix(ocap-kernel): make c-list import accounting symmetric #1010's claim that auditRefCounts is clean across kernel-test is correct; the topology simply isn't covered.
  • The leak is pre-existing. Remotes never received retireImport before fix(ocap-kernel): make c-list import accounting symmetric #1010 either. What changed is that the entry now carries a count, and there is now a checker that notices.
  • I have not verified whether remotes are meant to learn about retirement through a different channel — incarnation-change reconciliation, for instance. If they are, then getImporters is right as it stands and the correct fix is to widen the audit's exemption at refcount-audit.ts:116-124 instead. Someone who knows the remote GC protocol should decide which way this goes; the two fixes are not interchangeable.

Reproduction

A failing test exists locally, not yet pushed:

yarn vitest run --config packages/ocap-kernel/vitest.config.ts \
  packages/ocap-kernel/src/store/methods/refcount-audit.test.ts \
  -t "retires a remote importer" --coverage=false

It asserts that both importers receive a retireImport and that the audit is clean afterwards. Happy to push it, though which assertion is correct depends on the open question above, so I'd rather settle that first.

Found while reviewing #1010.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions