Skip to content

PipeWire backend: PipeWireBackendFactory::init() blocks the calling app forever if the daemon never completes registry syncs #1318

Description

@nilp0inter

Summary

PipeWireBackendFactory::init() can block the calling application forever when the PipeWire daemon accepts connections but never completes registry syncs. There is no timeout on the wait: a stalled or wedged PipeWire daemon turns any alcOpenDevice() call into a permanent hang of the host application's main thread.

We hit this in production with telegram-desktop on NixOS: the app froze at startup for an entire boot session because PipeWire came up in a state where client syncs never completed.

Environment

  • openal-soft 1.24.3 (nixpkgs package)
  • PipeWire 1.6.8 (client library and daemon)
  • Host app: telegram-desktop 7.0.2 (calls alcOpenDevice() during startup)
  • Linux x86_64, NixOS 26.11 unstable

Backtrace

From a core dump taken (SIGABRT) while the app was hung, ~20 s after launch:

#3  pthread_cond_wait@@GLIBC_2.3.2        libc.so.6
#4  loop_wait()                           libspa-support.so   (SPA data loop)
#5  PipeWireBackendFactory::init()        libopenal.so.1
#6  alc_initconfig()                      libopenal.so.1
#7  __pthread_once_slow                   libc.so.6
#8  ... app startup (main thread)

Other threads: two PipeWire do_loop/loop_iterate threads alive and polling (epoll_wait), so the backend's loop threads exist — the wakeup/done event that would release loop_wait never arrives.

Code path (alc/backends/pipewire.cpp @ master, 2dc741b)

bool PipeWireBackendFactory::init()
{
    ...
    pw_init(nullptr, nullptr);
    if(!gEventHandler.init())
        return false;

    if(!GetConfigValueBool({}, "pipewire", "assume-audio", false)
        && !gEventHandler.waitForAudio())   // ← blocks forever
auto waitForAudio() -> bool
{
    auto const plock = MainloopUniqueLock{mLoop};
    auto has_audio = false;
    plock.wait([this,&has_audio]
    {
        has_audio = mHasAudio.load(std::memory_order_acquire);
        return has_audio || initIsDone(std::memory_order_acquire);
    });
    return has_audio;
}

initIsDone is only set when the pw_core_sync round-trip started in EventManager::init() completes. In our failing state, PipeWire clients could connect but syncs never completed — pw-dump and wpctl status hung the same way from the command line, with no error in the PipeWire/WirePlumber journals. Result: waitForAudio() waits on the condition forever, and because this runs under pthread_once from the app's main thread during startup, the whole application is dead — window never appears, event loop never starts.

Reproduction

  1. Get the PipeWire daemon into a state where it accepts client connections but never answers registry syncs (we observed this after a session bring-up; pw-dump hanging is the tell).
  2. Run any app that calls alcOpenDevice() with the PipeWire backend enabled.
  3. App hangs permanently in PipeWireBackendFactory::init().

Restarting pipewire/wireplumber (making syncs complete again) makes the exact same binary launch instantly, which rules out the application and confirms the backend wait as the blocking point.

Observed impact

Because the first hung instance becomes the single-instance server without ever processing events, every later launch of the app connects as a second instance, sends its command, and hangs too — one wedged PipeWire state takes down the app for the whole session.

Suggestion

Bound the wait in waitForAudio() (and waitForInit()): e.g. wait on the PipeWire fd with a timeout, or use a timed condition wait, and treat a stalled sync as "no PipeWire support" (return false, kill() the handler) so the caller falls back to the next backend. Current master (2dc741b) still has the unbounded wait.

Workaround we verified: forcing another driver (drivers = pulse in alsoft.conf) skips the PipeWire backend entirely and the app starts instantly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions