Found reviewing #1023 (sirtimid/vat-lifecycle-consistency-v2).
What
runVat's new if (fatalError) throw fatalError; makes initializeAllVats' Promise.all reject, which rejects Kernel.#init — so the run loop never starts.
A single vat whose worker dies during initVat (a blocked iframe, a missing worker file, a bundle fetch dying mid-handshake) therefore takes down every other vat with it.
The rethrow is right for launchVat, whose caller needs the failure. It is wrong for startup, where #retireVat has already recorded that vat as dead and durably committed it, so the kernel could simply come up without it.
Suggested fix
Catch per vat in initializeAllVats, and only swallow once the vat is provably recorded dead — a platformServices.launch rejection leaves it store-active with no handle, which the first delivery to it turns into a dead run loop via #resolveEndpoint:
starts.push(
this.runVat(vatID, vatConfig).catch((error) => {
this.#logger.error(`Vat ${vatID} failed to start:`, error);
if (this.#kernelStore.isVatActive(vatID)) {
this.#retireVat(vatID, asError(error));
}
}),
);
Found reviewing #1023 (
sirtimid/vat-lifecycle-consistency-v2).What
runVat's newif (fatalError) throw fatalError;makesinitializeAllVats'Promise.allreject, which rejectsKernel.#init— so the run loop never starts.A single vat whose worker dies during
initVat(a blocked iframe, a missing worker file, a bundle fetch dying mid-handshake) therefore takes down every other vat with it.The rethrow is right for
launchVat, whose caller needs the failure. It is wrong for startup, where#retireVathas already recorded that vat as dead and durably committed it, so the kernel could simply come up without it.Suggested fix
Catch per vat in
initializeAllVats, and only swallow once the vat is provably recorded dead — aplatformServices.launchrejection leaves it store-active with no handle, which the first delivery to it turns into a dead run loop via#resolveEndpoint: