VatManager.#retireVat is the one synchronous step that records a vat's death: reject the promises it was deciding, drop the handle, release the root pin, deleteVat, markVatAsTerminated. Synchronous is deliberate — no crank can interleave with it. Atomic is a different property, and it does not hold.
Reached from terminateVat, it now runs while the store is held outside a crank, so there is no transaction around it at all: each write autocommits on its own statement. Reached from the run loop's own stopVat, it runs inside the crank's savepoints — grouped, but grouped with a delivery that may then be rolled back, which undoes the death while the handle stays deleted.
Either way a throw partway leaves a state the kernel cannot act on:
- after
deleteVat but before markVatAsTerminated: the vat is never eligible for nextTerminatedVatCleanup, so its whole c-list survives with nothing scheduled to reclaim it, and every object it imported keeps its counts forever. The reference count audit cannot see this — the counts are attributable to the surviving entries, so the store looks consistent.
- before
deleteVat: the store goes on calling the vat active with no handle for it, which #resolveEndpoint treats as a disagreement and kills the run loop over.
onCriticalFailure now falls back to markVatAsTerminated when the rest fails, which covers the second case from that one caller. It is a patch on one path, not the property.
The shape that would give the property is a savepoint around the writes, as RemoteHandle.handleRemoteMessage and RemoteManager's incarnation change already take. It does not drop in: the handle deletion is RAM and a rollback cannot undo it, so the ordering has to change too — the handle can only go once the records are durable, and the in-crank caller has the same constraint against the crank's own rollback.
Found by review of #1021 / #1022 / #1023.
VatManager.#retireVatis the one synchronous step that records a vat's death: reject the promises it was deciding, drop the handle, release the root pin,deleteVat,markVatAsTerminated. Synchronous is deliberate — no crank can interleave with it. Atomic is a different property, and it does not hold.Reached from
terminateVat, it now runs while the store is held outside a crank, so there is no transaction around it at all: each write autocommits on its own statement. Reached from the run loop's ownstopVat, it runs inside the crank's savepoints — grouped, but grouped with a delivery that may then be rolled back, which undoes the death while the handle stays deleted.Either way a throw partway leaves a state the kernel cannot act on:
deleteVatbut beforemarkVatAsTerminated: the vat is never eligible fornextTerminatedVatCleanup, so its whole c-list survives with nothing scheduled to reclaim it, and every object it imported keeps its counts forever. The reference count audit cannot see this — the counts are attributable to the surviving entries, so the store looks consistent.deleteVat: the store goes on calling the vat active with no handle for it, which#resolveEndpointtreats as a disagreement and kills the run loop over.onCriticalFailurenow falls back tomarkVatAsTerminatedwhen the rest fails, which covers the second case from that one caller. It is a patch on one path, not the property.The shape that would give the property is a savepoint around the writes, as
RemoteHandle.handleRemoteMessageandRemoteManager's incarnation change already take. It does not drop in: the handle deletion is RAM and a rollback cannot undo it, so the ordering has to change too — the handle can only go once the records are durable, and the in-crank caller has the same constraint against the crank's own rollback.Found by review of #1021 / #1022 / #1023.