[WebGPU] Fix OrtReleaseEnv self-deadlock after failed adapter request on Linux (#29591)
On Linux with the WebGPU EP, if `SessionOptionsAppendExecutionProvider`
is called when no Vulkan adapter is available, a subsequent
`OrtReleaseEnv` on the same thread hangs forever in a futex wait.
### Root causes
**1. C++ exceptions thrown inside Dawn `WaitAny` callbacks (primary)**
`ORT_ENFORCE` was called directly inside the
`RequestAdapter`/`RequestDevice` callback lambdas. Dawn's `WaitAny` does
not release its internal `EventManager` mutex on exception, so throwing
through it leaves that mutex permanently locked. The deadlock fires
later when `Cleanup()` calls `wgpuInstanceRelease` →
`EventManager::ShutDown()` → tries to re-acquire the same mutex on the
same thread.
**2. Zombie `WebGpuContext` left in the factory map (secondary)**
When `Initialize()` threw, the `WebGpuContext` entry remained in
`contexts_` with `ref_count=1` and no owner — a resource leak that would
also re-trigger the deadlock path at `Cleanup()`.
### Changes (`webgpu_context.cc`)
- **Adapter callback**: replace the throwing lambda with a non-throwing
one that writes into a local `RequestAdapterResult` struct;
`ORT_ENFORCE` is moved to *after* `WaitAny` returns:
```cpp
// Before — throws inside Dawn's callback dispatch:
[](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message, wgpu::Adapter* ptr) {
ORT_ENFORCE(status == wgpu::RequestAdapterStatus::Success, ...);
*ptr = std::move(adapter);
}, &adapter
// After — captures result, throws outside Dawn:
[](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message, RequestAdapterResult* result) {
result->status = status;
if (status == wgpu::RequestAdapterStatus::Success)
result->adapter = std::move(adapter);
else
result->message = std::string{message};
}, &adapter_result
// ORT_ENFORCE(adapter_result.status == ...) called here, after WaitAny
```
- **Device callback**: same pattern applied to `RequestDevice`.
- **`CreateContext` cleanup**: wrap `Initialize()` in a `try/catch`; on
failure decrement `ref_count` and erase the entry from `contexts_` if it
reaches zero.
### Motivation and Context
Fixes the hang reported when registering the WebGPU EP with no usable
Vulkan adapter and then calling `OrtReleaseEnv`. The process would park
on a futex with `__owner` set to its own TID — a non-recursive mutex
locked twice on the same thread — and never exit.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>