WASAPI: default-device activation fails with RPC_E_CHANGED_MODE after endpoint-notification callbacks put thread-pool threads into an STA
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 20/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- rust
- Ambito
- audio-video-rtc
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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).
- Lingua principale
- Rust
- Stelle
- 4k
- Fork
- 554
- Merge medio
- 2g 11h
- PR unite (30g)
- 3
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di RustAudio/cpal
-
aaudio: dynamic buffer tuning never grows the buffer (set_buffer_size_in_frames reports Err on success)Forse già presa @dannyward630 l’ha presa 1 giorno fa. Apertahost - aaudio platform - android
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
I maintainer di solito rispondono entro 1 giorno
-
WebAudio wasm build fails with wasm-bindgen panic=unwind closuresForse già presa @bogusyogi l’ha presa 9 giorni fa. Apertahost - webaudio platform - web
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
RustAudio/cpal#1394 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
host - pipewire platform - linux
Difficoltà 4/5 1-2 giorni Idoneità per principianti 52/100
RustAudio/cpal#1405 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
coreaudio (macOS): capture/playback timestamps double-count the buffer, loopback reads as −2 buffersForse già presa @Marco-Farruggio l’ha presa 12 giorni fa. Apertacorrectness host - coreaudio platform - macos
Difficoltà 4/5 3-5 giorni Idoneità per principianti 53/100
RustAudio/cpal#1398 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
ASIO: TimeBase treats small backward steps in systemTime as timeGetTime wraps, so stream instants race ahead and overflow after ~10 minutesForse già presa @chrischaps l’ha presa 16 giorni fa. Apertahost - asio platform - windows
Difficoltà 3/5 1-2 giorni Idoneità per principianti 35/100
RustAudio/cpal#1393 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di RustAudio/cpal
Issue simili
-
bug llm translation
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
accounts bug decoding
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
wboayue/rust-ibapi#1088 ·
I maintainer di solito rispondono entro 1 giorno
-
chmod -v: missing 'could not be accessed' line for operands that can't be stat'edForse già presa @emirmrkaljevic l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
gtk-rs/gtk-rs-core#2068 ·
I maintainer di solito rispondono entro 6 giorni