Skip to content

Windows: fatal 0xC000070A — camera enumeration (MF/mfksproxy) closes a handle with a threadpool wait still registered (0.5.9) #2115

Description

@aacarcrash

Summary

Cap 0.5.9 on Windows 11 dies repeatedly with a non-catchable fatal exception raised by
ntdll's threadpool waiter thread. I decoded two crash dumps (one triage, one full-memory) — a
HANDLE is closed while a threadpool wait is still registered on it, and the full dump
places the wait in Media Foundation's camera kernel-streaming layer (mfksproxy.dll,
mf.dll/mfplat.dll). It reproduces with no camera selected (cameraId: null) — device
enumeration alone triggers it.

9 crashes across ~1 hour of normal use, including one 4 seconds after launch.

I could not identify which component owns the handle (see What I could not determine), but the
failure mode itself is unambiguous.

I searched open and closed issues for 0xc000070a, INVALID_HANDLE, and threadpool and found
no match. The nearest existing report is #1954 (Windows crash on AMD 780M iGPU) — different
module, different exception code (0xC0000005 in atio6axx.dll), so I don't believe this is a
duplicate. Close it as one if you disagree.

Environment

Cap 0.5.9 (installed build, not self-compiled)
OS Windows 11 Home, build 10.0.26200
ntdll 10.0.26100.8972
GPU NVIDIA GeForce RTX 3070 Laptop (driver 32.0.15.8129) + AMD Radeon iGPU
WebView2 151.0.4129.78

The crash

Windows Error Reporting:

Faulting application name: Cap.exe, version: 0.5.9.0
Faulting module name:      ntdll.dll, version: 10.0.26100.8972
Exception code:            0xC000070A
Fault offset:              0x0000000000087154

0xC000070A is ntdll's fatal threadpool assertion, raised from the waiter thread. Its message
form is "Status 0x%x was returned, waiting on handle 0x%x for wait 0x%p, in waiter 0x%p" — and
the four values are carried in the exception record. From the minidump:

Exception parameter Value Meaning
0 0xC0000008 STATUS_INVALID_HANDLE
1 0x28 the handle being waited on
2 0x00000207_40931cf0 the TP_WAIT object
4 ntdll+0x4c6f0 the waiter

Interpretation: something registered a threadpool wait (SetThreadpoolWait /
RegisterWaitForSingleObject) on handle 0x28, then closed that handle without first
unregistering the wait. NtWaitForSingleObject on the waiter thread returned
STATUS_INVALID_HANDLE, and ntdll responded by raising a fatal exception. This kills the process
outright — it cannot be caught or recovered from.

The crashing thread is unnamed and its stack is entirely ntdll/KERNELBASE, consistent with a
threadpool waiter rather than application code. The 31 named threads at crash time (main,
15× tokio-runtime-worker, D3D Background Thread 0-3, EBWorkerThread, MojoThread,
sentry-transport) show the app was otherwise idle in the editor.

Frequency and crash sites

7 crashes in ~30 minutes. From cap-desktop.log, the last thing each session logged before dying:

# Last logged activity before death Time alive
1 editor open, captions loaded — then 20 s idle ~1 m 49 s
2 idle in editor ~10 m
3 mid editor-open — YUV pipelines created 5 s earlier ~1 m 18 s
4 idle in editor ~6 m
5 startup only — no window shown yet 15 s
6 5 failed save dialogs in 12 s (see below) ~10 m
7 mid list_windows_with_thumbnails 7 s

Sessions 5 and 7 died seconds after launch with no user interaction, so these are not user quits.

Only crash #1 produced a Windows Application Error event. I suspect WER bucket throttling
(identical signature repeating within minutes), but I have not confirmed that.

Possibly related: list_windows_with_thumbnails

Offered as an observation, not a diagnosis. In my logs this runs very frequently — dozens of
calls per minute, each re-selecting a DXGI adapter:

INFO list_windows_with_thumbnails: cap_d3d_adapter: Selected DXGI adapter by VRAM
     adapter=NVIDIA GeForce RTX 3070 Laptop GPU vendor_id="0x10DE" vram_mb=8020

GraphicsCapture.dll is loaded in the dump, and Windows.Graphics.Capture registers threadpool
waits on frame-arrival events. Rapid create/destroy of capture sessions is a plausible place for
a close-before-unregister race. Crash #7 occurred inside this call. That is circumstantial.

Second bug: export save dialog silently no-ops

Separate from the crash, and reproducible:

13:24:45.663  INFO cap_desktop_lib::export: Showing save file dialog file_name="....mp4"
13:24:45.664  INFO cap_desktop_lib::export: Save file dialog completed path=None

0.8 ms between "showing" and "completed". The native dialog never opened. This repeated 5
times in 12 seconds as I retried, then the process died.

Two distinct problems:

  1. The dialog fails to open. Root cause unknown — needs investigation on the Windows side.
  2. The failure is indistinguishable from a user cancel. In
    apps/desktop/src-tauri/src/export.rs (~line 1289), tauri-plugin-dialog's save_file()
    callback yields Option<PathBuf>, and both "user cancelled" and "dialog failed to open" arrive
    as None. Cap logs path=None and silently does nothing.

From a user's perspective: you click Export, nothing happens, no error appears, so you click
again. Surfacing the underlying error instead of swallowing it would be a small, self-contained
improvement — and would have made this far easier to report.

Minor: crash telemetry under-reports

crash_sentinel::mark_clean_exit() appears to have a single call site in
apps/desktop/src-tauri/src/lib.rs. Exits through any other path leave session-active.json
behind and get reported as unclean on next launch. This cuts both ways — it adds noise to real
crash data. Low priority, mentioned only because it made the logs harder to interpret.

UPDATE: full dump captured — the wait belongs to Media Foundation's camera layer

After enabling LocalDumps with DumpType=2 I caught the crash again (9th occurrence) and got a
full-memory dump. Same signature, one variation:

Exception parameter First dump Full dump
0 (wait status) 0xC0000008 STATUS_INVALID_HANDLE 0xC0000022 STATUS_ACCESS_DENIED
1 (handle) 0x28 0x30

Both statuses from the same wait = classic close-while-registered: INVALID_HANDLE when the slot
is empty, ACCESS_DENIED when the slot has been recycled by a new object without SYNCHRONIZE
rights.

This time the TP_WAIT object was in the captured memory:

  • The two heap objects adjacent to the TP_WAIT carry vtables in mf.dll and
    mfplat.dll.
  • The crashing waiter thread's stack contains repeated frames in mfksproxy.dll — the Media
    Foundation kernel-streaming (camera) proxy — i.e. this waiter had been servicing camera KS
    event waits.

So: a camera-device event handle was closed while its MF work-queue wait was still registered.

Two more data points that narrow it further:

  • The crash reproduced with every camera deselected — my store has "cameraId": null
    and also after I disabled the broken Camo device. Camera enumeration alone is sufficient.
  • Reading the source: cap_camera_windows::get_devices() runs on every camera listing, and
    CameraInfo::formats_impl() activates each device's media source and builds a source reader
    just to read formats. crates/camera-mediafoundation/src/lib.rs sets
    MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN (the comment says "Media source shuts
    down on drop if this isn't specified") — which means the media source outlives the reader
    and is never explicitly Shutdown()
    . Teardown then happens by COM refcount on whatever
    thread releases last, racing the KS event waits the frame server still has registered.
    I believe that is the bug, or very close to it.

Suggested direction (happy to be corrected by someone who knows this code):

  • call IMFMediaSource::Shutdown() deterministically before the last release in the
    enumeration/formats path, or
  • don't activate media sources at all during enumeration (MFEnumDeviceSources attributes +
    IMFActivate metadata are enough to list devices; formats could be read lazily), and
  • rate-limit get_devices() — it currently runs per listing call.

What I could not determine

  • Which exact release site closes the handle. Naming that needs symbols/AppVerifier; the
    module attribution above is from the dump's memory, which is solid, but the code path is
    inferred from reading the crates.
  • Whether the failed export dialogs caused crash fix: add missing dependencies #6 or merely preceded it.
  • My machine's device roster (a Camo virtual camera that was stuck in an Error PnP state, a
    phantom Yealink conference camera, two virtual display adapters) may make the race easier to
    hit — but the crash occurred with Camo disabled, so it is not required.

Also seen (probably unrelated, flagging in case it isn't)

Every hardware H.264 encoder fails pre-flight on this machine, so recording falls back to CPU:

WARN h264_nvenc failed pre-flight: ffmpeg::Error(1: Operation not permitted)
WARN h264_mf    failed pre-flight: ffmpeg::Error(22: Invalid argument)
WARN h264_qsv   failed pre-flight: ffmpeg::Error(1313558101: Unknown error occurred)
WARN h264_amf   failed pre-flight: ffmpeg::Error(1313558101: Unknown error occurred)
WARN Using SOFTWARE H264 encoder (high CPU usage expected) encoder=libx264 1852x916 fps=60.0

nvEncodeAPI64.dll is present and the RTX 3070 is selected for D3D, so NVENC failing is
surprising. The bundled FFmpeg DLLs are dated Oct 2024. Software x264 at 60 fps also produced
sustained Failed to send decoded frame N: receiver dropped warnings during playback.

Possibly in scope of #976 (hardware encoding on Windows via Media Foundation) — h264_mf is
failing here with Invalid argument. Happy to split this into its own issue if you'd prefer.

Happy to provide

  • Full cap-desktop.log and cap-desktop-errors.log (793 lines)
  • The 74 MB minidump, on request
  • A full-memory dump — I've now enabled LocalDumps with DumpType=2, so I can capture one that
    actually contains the TP_WAIT object and name the owning module. Say the word and I'll
    reproduce and follow up.

Note: Cap ships Sentry (there's a sentry-transport thread), so you may already have aggregate
data on this crash bucket. WER bucket ID was 1840226820931017906, hashed bucket
4ef061879dbcc95bf989cc8b2fbd5cb2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions