diff --git a/Sources/JavaScriptEventLoop/JobQueue.swift b/Sources/JavaScriptEventLoop/JobQueue.swift index a0f2c4bbb..6c91af25d 100644 --- a/Sources/JavaScriptEventLoop/JobQueue.swift +++ b/Sources/JavaScriptEventLoop/JobQueue.swift @@ -42,6 +42,15 @@ extension JavaScriptEventLoop { func runAllJobs() { assert(queueState.isSpinning) + // `defer`, so the latch is released even if a job throws. + // + // `runSynchronously` can unwind — a Swift runtime trap, or (under + // JavaScriptKit specifically) a JS exception crossing back into wasm. + // Clearing `isSpinning` only by falling off the end of the loop leaves + // it latched `true` on that path, and `insertJobQueue` then never + // schedules another drain: the executor is dead for the lifetime of the + // process, silently. + defer { queueState.isSpinning = false } while let job = self.claimNextFromQueue() { #if compiler(>=5.9) @@ -50,8 +59,6 @@ extension JavaScriptEventLoop { job._runSynchronously(on: self.asUnownedSerialExecutor()) #endif } - - queueState.isSpinning = false } func claimNextFromQueue() -> UnownedJob? {