cpal::Stream::new_playback spawns a thread that runs block_on(stream.play_all()).
Internally play_all is source_eof().await?; drain().await?; : it waits for the cpal callback to return 0 bytes. But the CallbackWrapper in the pulseaudio crate (0.3.1) always returns buf.len(), never 0.
So that thread blocks forever, holds an Arc<InnerPlaybackStream> clone, and InnerPlaybackStream::drop (which sends DeletePlaybackStream) never fires.
Result: every playback = one leaked PA stream plus one stuck OS thread.
Stream::drop only calls handle.cancel(), which unblocks the latency-monitor thread, not the play_all thread.

cpal::Stream::new_playbackspawns a thread that runsblock_on(stream.play_all()).Internally
play_allissource_eof().await?; drain().await?;: it waits for the cpal callback to return 0 bytes. But theCallbackWrapperin the pulseaudio crate (0.3.1) always returnsbuf.len(), never 0.So that thread blocks forever, holds an
Arc<InnerPlaybackStream>clone, andInnerPlaybackStream::drop(which sendsDeletePlaybackStream) never fires.Result: every playback = one leaked PA stream plus one stuck OS thread.
Stream::droponly callshandle.cancel(), which unblocks the latency-monitor thread, not theplay_allthread.