fix(audio): keep the PipeWire context for the next backend

pull/18/head
reionwong 2 weeks ago
parent 7eff672913
commit c5d98ad24a

@ -8,6 +8,7 @@
#include <charconv>
#include <chrono>
#include <cstring>
#include <mutex>
#include <sys/epoll.h>
#include <spa/param/audio/raw.h>
@ -249,6 +250,16 @@ const pw_metadata_events Backend::metadataEvents = [] {
return events;
}();
// The last backend's loop and context, for the next one on the thread. A
// context's modules allocate on load what unloading them never frees, so a
// page that comes and goes would otherwise leak with each context.
struct SpareContext
{
pw_loop* loop = nullptr;
pw_context* context = nullptr;
};
static thread_local SpareContext s_spare;
std::shared_ptr<Backend> Backend::shared()
{
static std::weak_ptr<Backend> instance;
@ -263,7 +274,9 @@ std::shared_ptr<Backend> Backend::shared()
Backend::Backend()
: m_eventLoop(base::EventLoop::current())
{
pw_init(nullptr, nullptr);
// Once per process, never undone: the spare context outlives every backend.
static std::once_flag initialized;
std::call_once(initialized, [] { pw_init(nullptr, nullptr); });
m_retry.setSingleShot(true);
m_retry.onTimeout = [this] { connectToDaemon(); };
m_failure.setSingleShot(true);
@ -278,8 +291,11 @@ Backend::~Backend()
m_retry.stop();
m_failure.stop();
disconnectFromDaemon();
if (m_context && !s_spare.context) {
s_spare = {std::exchange(m_loop, nullptr), std::exchange(m_context, nullptr)};
return;
}
releaseContext();
pw_deinit();
}
std::vector<std::shared_ptr<AudioObject>> Backend::objects() const
@ -313,6 +329,10 @@ void Backend::connectToDaemon()
if (m_core)
return;
m_failurePending = false;
if (!m_loop && s_spare.context) {
m_loop = std::exchange(s_spare.loop, nullptr);
m_context = std::exchange(s_spare.context, nullptr);
}
// A context loads its modules and configuration when created; kept across
// attempts, a retry while the daemon is away costs only the connect.
if (!m_loop) {

@ -530,3 +530,18 @@ TEST_F(AudioTest, Reconnect)
sink->setVolume(0.3);
ASSERT_TRUE(until([&] { return near(sink->volume(), 0.3); }));
}
// As a page that shows sound opens and closes: each backend reads the graph
// and is the last one to go.
TEST_F(AudioTest, LastBackendComesAndGoes)
{
drivers.clear();
backend.reset();
for (int i = 0; i < 5; ++i) {
backend = Backend::shared();
ASSERT_TRUE(waitFor(*loop, [] { return backend->isConnected() && find("test-output-a"); }, 10s));
backend.reset();
}
backend = Backend::shared();
ASSERT_TRUE(waitFor(*loop, [] { return backend->isConnected() && find("test-output-a"); }, 10s));
}

Loading…
Cancel
Save