JSModuleLoader: propagate a TerminationException from resolve() instead of treating it as a resolution failure
Host resolve() — and JSC's own JSModuleLoader::resolve(const Identifier&, …) wrapper around it, whose RETURN_IF_EXCEPTIONs after the jsString conversions run before the hook is even called — can surface a TerminationException: RETURN_IF_EXCEPTION services VM traps, so a terminate() request from another thread materializes at whatever exception check runs next. No embedder code needs to be involved.
hostLoadImportedModule's resolution-error path then
- attached error info to the termination exception and cached it as the specifier's resolution failure,
- called
promise->rejectWithCaughtException(vm, scope), which by design refuses to clear aTerminationExceptionand leaves it pending, - continued into
finishLoadingImportedModule(...)with the exception still set — for a dynamic import that reachescontinueDynamicImport, whosepromise->reject(...); scope.assertNoException();aborts.
Every other rejectWithCaughtException() in this file is wrapped in RETURN_IF_EXCEPTION(scope, ...) so a termination propagates; this path was missing the equivalent. The fix returns early (null promise, exception pending) when the caught exception is the termination exception; both callers (loadModule, innerModuleLoading) already handle that shape. Upstream main has the same code: https://github.com/WebKit/WebKit/blob/9ef04dabf52d7432a3e7ab13f44f9d8b4bd933a0/Source/JavaScriptCore/runtime/JSModuleLoader.cpp#L646 . An audit of the other catch-and-convert sites in the module machinery (JSMicrotask.cpp module-loading tasks, CyclicModuleRecord.cpp, Completion.cpp, JSWebAssembly.cpp, globalFuncImportModule) found they all return immediately after the catch; this is the only one that continues.
Observed in Bun as an intermittent ASSERTION FAILED: !exception() (ExceptionScope.h:61) on the ASAN lane when worker.terminate() lands while the worker thread is resolving its preload module:
JSC::ExceptionScope::assertNoException ExceptionScope.h:61
JSC::JSModuleLoader::continueDynamicImport JSModuleLoader.cpp:1037
JSC::JSModuleLoader::finishLoadingImportedModule JSModuleLoader.cpp:970
JSC::JSModuleLoader::hostLoadImportedModule JSModuleLoader.cpp:673
JSC::JSModuleLoader::loadModule JSModuleLoader.cpp:801
JSC::moduleLoadTopSettled JSMicrotask.cpp:1193
runInternalMicrotask → JSC::VM::drainMicrotasks