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
This commit is contained in:
Wim Taymans 2026-06-01 10:34:40 +02:00
parent f22932580f
commit b41d117609
2 changed files with 14 additions and 0 deletions

View file

@ -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);

View file

@ -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);