Windows: fatal 0xC000070A — camera enumeration (MF/mfksproxy) closes a handle with a threadpool wait still registered (0.5.9)
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- rust, tauri
調査の方向性
crates/camera-mediafoundation/src/lib.rs の cap_camera_windows::get_devices() と CameraInfo::formats_impl() から開始し、Media Foundation source-reader の teardown を追跡して、cameraId を null に設定した状態で Windows のクラッシュを再現します。提供されたダンプとログを使用して、カメラ列挙パスが wait の登録中にハンドルを閉じなくなったことを確認します。apps/desktop/src-tauri/src/export.rs のエクスポートダイアログの問題は別スコープであり、この修正と混在させないでください。
索引モデルが issue の本文から書いたものです。
説明
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:
- The dialog fails to open. Root cause unknown — needs investigation on the Windows side.
- The failure is indistinguishable from a user cancel. In
apps/desktop/src-tauri/src/export.rs(~line 1289),tauri-plugin-dialog'ssave_file()
callback yieldsOption<PathBuf>, and both "user cancelled" and "dialog failed to open" arrive
asNone. Cap logspath=Noneand 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_WAITcarry vtables inmf.dlland
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
storehas"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.rssets
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 explicitlyShutdown(). 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 (
MFEnumDeviceSourcesattributes +
IMFActivatemetadata 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 #6 or merely preceded it.
- My machine's device roster (a Camo virtual camera that was stuck in an
ErrorPnP 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.logandcap-desktop-errors.log(793 lines) - The 74 MB minidump, on request
- A full-memory dump — I've now enabled
LocalDumpswithDumpType=2, so I can capture one that
actually contains theTP_WAITobject 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.
- 主要言語
- Rust
- スター
- 22.5k
- フォーク
- 1.9k
- 平均マージ
- 6時間 33分
- マージ済み PR(30日)
- 77
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
CapSoftware/Cap のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
CapSoftware/Cap#2305 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
CapSoftware/Cap#1714 · コメント 3 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
CapSoftware/Cap#2329 · コメント 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
CapSoftware/Cap#2328 · コメント 1 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 67/100
CapSoftware/Cap#2324 · コメント 1 件 ·
CapSoftware/Cap の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
issue
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
agentic-workflows
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
web-infra-dev/rspack#15847 ·