WASAPI: default-device activation fails with RPC_E_CHANGED_MODE after endpoint-notification callbacks put thread-pool threads into an STA
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 20/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- rust
調査の方向性
Start in src/host/wasapi/stream.rs at DefaultDeviceNotificationImpl::OnDeviceRemoved and OnDeviceStateChanged, then follow get_current_default into current_default_endpoint in device.rs to where com_initialized() is called. Run the reproduction in the issue, which polls the default output device after thread-pool callbacks, and confirm RPC_E_CHANGED_MODE on the second open. Done means the default device opens after those callbacks run, without changing the calling thread's apartment.
索引モデルが issue の本文から書いたものです。
説明
cpal version: 0.18.2 (WASAPI host), Windows 11
What happens
After some time, opening the default input or output device fails for the rest of the process:
Failed to get audio client: Cannot change thread mode after it is set. (os error -2147417850)
(0x80010106 = RPC_E_CHANGED_MODE). Devices obtained from input_devices() / output_devices() (specific IMMDevices) keep working; only default_input_device() / default_output_device() fail. Only a restart of the process helps.
Cause
DefaultDeviceNotificationImpl::OnDeviceRemovedandOnDeviceStateChanged(src/host/wasapi/stream.rs) callget_current_default→current_default_endpoint(device.rs), which callscom::com_initialized().- These callbacks run on threads owned by Windows (MMDevAPI / thread pool), not on cpal's threads.
com_initialized()callsCoInitializeEx(COINIT_APARTMENTTHREADED)there. On a pool thread that has no COM initialization of its own this succeeds, so the pool thread becomes an STA. The thread-local guard is only dropped when the thread exits, which pool threads normally do not, so the thread stays STA. - For the default device, cpal activates via
ActivateAudioInterfaceAsync(DEVINTERFACE_AUDIO_*)(activate_audio_interface_sync). When the process has a multithreaded apartment (any thread didCoInitializeEx(COINIT_MULTITHREADED), which many libraries do), the activation runs on the thread pool and fails withRPC_E_CHANGED_MODEon a pool thread that cpal turned into an STA.
Reproduction
Simulates step 1–2 by calling cpal's own path on pool threads (what the notification callbacks do):
use cpal::traits::{DeviceTrait, HostTrait};
use std::sync::atomic::{AtomicUsize, Ordering};
use windows::Win32::System::{Com::*, Threading::*};
static DONE: AtomicUsize = AtomicUsize::new(0);
unsafe extern "system" fn cb(_: PTP_CALLBACK_INSTANCE, _: *mut core::ffi::c_void) {
let _ = cpal::default_host().default_output_device(); // -> current_default_endpoint -> com_initialized
std::thread::sleep(std::time::Duration::from_millis(200));
DONE.fetch_add(1, Ordering::SeqCst);
}
fn open() {
std::thread::spawn(|| {
let d = cpal::default_host().default_output_device().unwrap();
println!("{:?}", d.supported_output_configs().map(|c| c.count()));
})
.join()
.unwrap();
}
fn main() {
// Some other thread in the MTA, as libraries commonly have.
std::thread::spawn(|| {
let _ = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
loop { std::thread::park(); }
});
std::thread::sleep(std::time::Duration::from_millis(100));
open(); // Ok(..)
for _ in 0..64 {
unsafe { TrySubmitThreadpoolCallback(Some(cb), None, None).unwrap() };
}
while DONE.load(Ordering::SeqCst) < 64 {
std::thread::sleep(std::time::Duration::from_millis(10));
}
open(); // Err(Failed to get audio client: Cannot change thread mode after it is set. ...)
}
Observed: the first open() succeeds, every later one fails with RPC_E_CHANGED_MODE. Without the MTA thread both succeed. Calling a specific device's supported_output_configs() after the poisoning still succeeds.
Suggested fix
Code that runs on threads cpal does not own (the IMMNotificationClient callbacks, Drop of DefaultDeviceMonitor if it can run elsewhere) should not change that thread's apartment. For example, current_default_endpoint could skip com_initialized() when called from the callbacks, or join the MTA there (CoInitializeEx(COINIT_MULTITHREADED) / CoIncrementMTAUsage) instead of making the thread an STA. (Not verified: on which threads MMDevAPI invokes the callbacks; the reproduction above uses the default thread pool.)
Related: #1302 (enumerator bound to the first thread's apartment).
- 主要言語
- Rust
- スター
- 4k
- フォーク
- 554
- 平均マージ
- 2日 11時間
- マージ済み PR(30日)
- 3
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
RustAudio/cpal のほかの issue
-
aaudio: dynamic buffer tuning never grows the buffer (set_buffer_size_in_frames reports Err on success)対応中かも @dannyward630 が 1 日前に担当しました。 オープンhost - aaudio platform - android
難易度 2/5 1〜3時間 初心者へのやさしさ 79/100
メンテナーはふだん 1 日以内に返信
-
WebAudio wasm build fails with wasm-bindgen panic=unwind closures対応中かも @bogusyogi が 10 日前に担当しました。 オープンhost - webaudio platform - web
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
RustAudio/cpal#1394 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
code quality dependencies host - wasapi platform - windows
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
RustAudio/cpal#1416 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
host - pipewire platform - linux
難易度 4/5 1〜2日 初心者へのやさしさ 52/100
RustAudio/cpal#1405 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
coreaudio (macOS): capture/playback timestamps double-count the buffer, loopback reads as −2 buffers対応中かも @Marco-Farruggio が 12 日前に担当しました。 オープンcorrectness host - coreaudio platform - macos
難易度 4/5 3〜5日 初心者へのやさしさ 53/100
RustAudio/cpal#1398 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
status:untriaged
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
midnightntwrk/midnight-zk#592 ·
メンテナーはふだん 3 日以内に返信
-
QuartoHub: have the Document name before the filename in the browser tab対応中かも @cscheid が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
quarto-dev/q2#930 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
muse P3
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
HerringtonDarkholme/hd-lang#923 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信