Skip to content

WebGPU plugin EP: ReleaseEpFactory throws "Unknown exception" on macOS during teardown (WebGpuContextFactory::Cleanup is not exception-safe) #29206

Description

Describe the issue

On macOS (arm64), tearing down a process that has registered the WebGPU plugin EP (libonnxruntime_providers_webgpu.dylib) logs an error:

[error] ... [ep_library_plugin.cc:69 Unload] ReleaseEpFactory failed for: ".../webgpu-ep/libonnxruntime_providers_webgpu.dylib" with error: Unknown exception

It is emitted when the host calls the public C API UnregisterExecutionProviderLibrary(env, name) (or when the last OrtEnv reference is released and Environment::~Environment() auto-unregisters remaining EP libraries).

The process still exits, but (1) the error is noisy/misleading and (2) the underlying WebGpuContextFactory::Cleanup() is not exception-safe, which is a real resource leak if an EP is registered/unregistered repeatedly in a long-lived process.

Root cause

The call chain:

  1. EpLibraryPlugin::Unload() calls the plugin's exported ReleaseEpFactory and logs any non-OK status — onnxruntime/core/session/plugin_ep/ep_library_plugin.cc:69.
  2. The WebGPU plugin's ReleaseEpFactory (onnxruntime/core/providers/webgpu/ep/api.cc:63-81) calls CleanupWebGpuContexts()WebGpuContextFactory::Cleanup().
  3. WebGpuContextFactory::Cleanup() (onnxruntime/core/providers/webgpu/webgpu_context.cc:1049-1061) does delete contexts_ and wgpuInstanceRelease(default_instance_), which tears down Dawn's Metal device/instance.
  4. On macOS, that Metal teardown raises an Objective-C NSException. ObjC exceptions propagate through C++ frames on arm64 and are caught only by catch (...), so ReleaseEpFactory's EXCEPTION_TO_RETURNED_STATUS_END converts it to the OrtStatus("Unknown exception") that gets logged. (Not seen on Linux/Vulkan or Windows/D3D12 — Metal-specific.)

Two distinct problems:

  • Cosmetic: the caught teardown exception surfaces as an ERROR log even though the unload is best-effort and otherwise succeeds.
  • Correctness (the more important one): Cleanup() is not exception-safe. If delete contexts_ throws, then contexts_ is not nulled, default_instance_ is never released, and default_instance_ is not nulled. The statics are left dangling, leaking a WGPUInstance (and its Dawn/Metal InstanceBase/physical-device state) on every register→unregister cycle, and breaking a subsequent re-init (CreateContext would reuse a partially-destroyed map).

The current Cleanup() is identical on v1.25.1 and main (6be94de). The plugin EP README already notes WebGPU cleanup as incomplete (onnxruntime/core/providers/webgpu/ep/README.md → "Missing parts": "need a way to do WebGPU cleanup …").

Proposed fix

Make WebGpuContextFactory::Cleanup() exception-safe and idempotent: detach the static state first (so the factory is left clean/reusable even if a destructor throws), then release each resource under an independent guard so a throw in one cannot leak the other.

void WebGpuContextFactory::Cleanup() {
  std::lock_guard<std::mutex> lock(mutex_);

  // Detach static state first so the factory is left clean/reusable even if a
  // destructor throws (on macOS, Dawn's Metal teardown can raise an Objective-C
  // NSException). Guard each release independently so a throw in one cannot
  // leak the other or leave the statics dangling.
  auto* contexts = contexts_;
  contexts_ = nullptr;
  WGPUInstance instance = default_instance_;
  default_instance_ = nullptr;

  try {
    delete contexts;
  } catch (...) {
    LOGS_DEFAULT(WARNING) << "Exception while destroying WebGPU contexts during teardown; ignoring.";
  }

  if (instance != nullptr) {
    try {
      wgpuInstanceRelease(instance);
    } catch (...) {
      LOGS_DEFAULT(WARNING) << "Exception while releasing WebGPU instance during teardown; ignoring.";
    }
  }
}

This removes the misleading ERROR (the plugin's ReleaseEpFactory now returns OK) and, more importantly, guarantees the instance is released and the statics are nulled even when Metal teardown throws. A deeper fix (wrapping the Metal teardown in @try/@catch so Dawn doesn't throw at all) could follow, but the exception-safety fix is correct and minimal on its own.

Urgency

Low for single-shot processes (exits cleanly); higher for long-lived hosts that register/unregister EPs repeatedly (cumulative WGPUInstance/Metal leak).

Platform / version

  • ONNX Runtime 1.25.1 (reproduced); Cleanup() unchanged on main (6be94de).
  • macOS (Apple Silicon, arm64), Metal backend.
  • WebGPU plugin EP (shared-library) build, registered via RegisterExecutionProviderLibrary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ep:WebGPUort-web webgpu providerplatform:mobileissues related to ONNX Runtime mobile; typically submitted using templateplatform:webissues related to ONNX Runtime web; typically submitted using template

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions