jack: fix race in get_buffer_output()

When a single output port is linked to multiple input ports of the same
client and the client uses multiple threads to process the input ports,
get_buffer_output() is called from multiple threads concurrently and
causes a race.

Multiple threads will try to dequeue a buffer concurrently and set the
HAVE_DATA io status, which causes the port to run out of buffers quickly
and the io are to become corrupted.

Use CAS to make sure only one thread dequeues and sets the io status.
The other concurrent threads will spin until there is a buffer. The fast
path will be that the buffer is already dequeued and then it is simply
reused.

Fixes #5324
This commit is contained in:
Wim Taymans 2026-06-25 11:21:01 +02:00
parent d97a9bf44b
commit 2a4222a538

View file

@ -248,6 +248,7 @@ struct mix {
struct port *peer_port;
struct spa_io_buffers *io[2];
uint32_t io_busy;
struct spa_list queue;
struct buffer buffers[MAX_BUFFERS];
@ -652,6 +653,7 @@ static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32
mix->io[0] = mix->io[1] = NULL;
mix->n_buffers = 0;
spa_list_init(&mix->queue);
SPA_ATOMIC_STORE(mix->io_busy, 0);
if (mix_id == SPA_ID_INVALID) {
port->global_mix = mix;
if (port->n_mix > 0)
@ -733,6 +735,7 @@ static int clear_buffers(struct client *c, struct mix *mix)
}
mix->n_buffers = 0;
spa_list_init(&mix->queue);
SPA_ATOMIC_STORE(mix->io_busy, 0);
return 0;
}
@ -1724,11 +1727,11 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
if (SPA_UNLIKELY((io = mix->io[cycle]) == NULL || mix->n_buffers == 0))
return NULL;
if (io->status == SPA_STATUS_HAVE_DATA &&
if (SPA_ATOMIC_LOAD(io->status) == SPA_STATUS_HAVE_DATA &&
io->buffer_id < mix->n_buffers) {
b = &mix->buffers[io->buffer_id];
d = &b->datas[0];
} else {
} else if (SPA_ATOMIC_CAS(mix->io_busy, 0, 1)) {
if (mix->n_buffers == 1) {
b = &mix->buffers[0];
} else {
@ -1739,6 +1742,7 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
if (SPA_UNLIKELY(b == NULL)) {
pw_log_warn("port %p: out of buffers %d", p, mix->n_buffers);
io->buffer_id = SPA_ID_INVALID;
SPA_ATOMIC_STORE(mix->io_busy, 0);
return NULL;
}
}
@ -1748,7 +1752,15 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t
d->chunk->stride = stride;
io->buffer_id = b->id;
io->status = SPA_STATUS_HAVE_DATA;
SPA_ATOMIC_STORE(io->status, SPA_STATUS_HAVE_DATA);
SPA_ATOMIC_STORE(mix->io_busy, 0);
} else {
while (SPA_ATOMIC_LOAD(io->status) != SPA_STATUS_HAVE_DATA)
;
if (SPA_UNLIKELY(io->buffer_id >= mix->n_buffers))
return NULL;
b = &mix->buffers[io->buffer_id];
d = &b->datas[0];
}
ptr = d->data;
if (buf)