fix(tsconnect): stop reporting a deliberate shutdown as a panic

The exit handler called onExit("Unexpected shutdown") whenever the Go
runtime exited. That was upstream's wording from when nothing could stop
the runtime, so every exit really was a panic. This fork added shutdown(),
so a clean teardown now reports itself as a crash to the panic handler
createIPN() hands to its callers.

Split the two cases. Before the IPN reaches the caller an exit is a
startup failure, and rejecting hands it back as an error rather than as a
side-channel callback. After that the caller holds the only shutdown
path, so report the exit without claiming it was unexpected.

Found in review of webnet/webnet#188.

Co-Authored-By: claude-opus-5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 01:27:21 +00:00
co-authored by Claude
parent 3c63a95446
commit 738fea52f8
+18 -4
View File
@@ -27,15 +27,29 @@ export async function startIPN(
})
go.env[INIT_CALLBACK_ENV] = name
const exited = go.run(instance).then(() => {
// An exit before the IPN reaches the caller is a startup failure, and throwing
// hands it back as a rejection. Afterwards the caller holds the only shutdown
// path, so an exit is either that shutdown or a panic; report it either way,
// because the IPN is dead in both cases.
let handedOver = false
const exited: Promise<never> = go.run(instance).then(() => {
delete globals[name]
onExit("Unexpected shutdown")
throw new Error("Go runtime exited before the IPN was ready")
if (handedOver) onExit("Go runtime exited")
// Always reject: before the handover this is what fails the race below,
// and after it the race has settled, so nothing observes the rejection.
throw new Error(
handedOver
? "Go runtime exited"
: "Go runtime exited before the IPN was ready"
)
})
const [newIPN, terminate] = await Promise.race([ready, exited])
try {
return await newIPN(config)
const ipn = await newIPN(config)
handedOver = true
return ipn
} catch (err) {
// Nothing was built, so nothing can shut the runtime down. Exit it here and
// wait for it, or the page keeps a blocked runtime for a failed startup.