From b41d117609ffa2dcb9c80adecdf0a84934e5b076 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 1 Jun 2026 10:34:40 +0200 Subject: [PATCH] impl-port: make suspend go from INIT -> CONFIGURE Bring the port to INIT before going to CONFIGURE when we do the suspend logic. This is to ensure that there always is a state change to emit the state change notification. This was, the link can detect the suspend case and cancel any pending results. One of the problem with suspend is that the state changes on the ports are done from different places; the port and link. This causes issues like: 1. do_negotiate calls pw_port_set_param(Format,..) with the negotiated format. This returns async and the link queues a complete_ready callback. 2. The node is suspended, pw_impl_port_set_param(Format, NULL) is called to clear the port format. This bypasses the link. 3. The reply from step 2 arrives and triggers complete_ready, this brings the port state to READY and the link state to ALLOCATING. 4. The link continues allocating and sets buffers on the port. This then fails because the last format set was NULL. Ideally all port states should be managed in one place and the async port state changes should be kept in the port itself as well but this will need some more work. Fixes #3547 --- src/pipewire/impl-link.c | 13 +++++++++++++ src/pipewire/impl-port.c | 1 + 2 files changed, 14 insertions(+) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index b7c401280..e465ef86a 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -1049,15 +1049,28 @@ static void port_state_changed(struct pw_impl_link *this, struct pw_impl_port *p struct pw_impl_port *other, enum pw_impl_port_state old, enum pw_impl_port_state state, const char *error) { + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct port_info *info; + pw_log_debug("%p: port %p old:%d -> state:%d prepared:%d preparing:%d", this, port, old, state, this->prepared, this->preparing); + if (port == impl->output.port) + info = &impl->output; + else + info = &impl->input; + switch (state) { case PW_IMPL_PORT_STATE_ERROR: link_update_state(this, PW_LINK_STATE_ERROR, -EIO, error ? strdup(error) : NULL); break; case PW_IMPL_PORT_STATE_INIT: case PW_IMPL_PORT_STATE_CONFIGURE: + if (old == PW_IMPL_PORT_STATE_INIT) { + port_set_busy_id(this, info, SPA_ID_INVALID, SPA_ID_INVALID); + pw_work_queue_cancel(impl->work, info, SPA_ID_INVALID); + old = PW_IMPL_PORT_STATE_READY; + } if (this->prepared || state < old) { this->prepared = this->preparing = false; link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 0a7af2c93..c4a654ab7 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1615,6 +1615,7 @@ void pw_impl_port_suspend(struct pw_impl_port *port) int res; if ((res = pw_impl_port_set_param(port, SPA_PARAM_Format, 0, NULL)) < 0) pw_log_warn("%p: error unset format: %s", port, spa_strerror(res)); + port->state = PW_IMPL_PORT_STATE_INIT; /* force CONFIGURE in case of async, use update_state to * notify links so they can cancel pending work */ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL);