alsa-plugin: prevent deadlock when update_active is called from two threads

When there is not enough room for next data to write, alsa calls
snd_pcm_pipewire_poll_descriptors to setup file descriptor. This function
clears io->poll_fd by calling spa_system_eventfd_read and next time the
quantum is processed, update_active sets value in io->poll_fd,
alsa is notified and can continue to push data.

In bad case scenario, update_active is called simultaneously from
both - alsa thread and pw thread. In alsa thread, the check_active(io)
returns false, pw->active is set to false, but spa_system_eventfd_read
isn't called yet as Alsa thread is rescheduled. Pw thread starts to execute
the same code, however this time, check_active(io) returns true, pw->active
is set to true and spa_system_eventfd_write is called. When alsa thread
starts to run again, spa_system_eventfd_read is called and clears any
events from io->poll_fd.
Alsa starts to poll for events, io->poll_fd is clear, pw->active is set
to true and therefore spa_system_eventfd_write is not called ever again.

To fix this deadlock, write to io->poll_fd every time there is some
room for new data. Doing this is safe, as write only increases internal
counter and next read clears the counter.
This code can lead to opposite behavior - spa_system_eventfd_write is
called right after spa_system_eventfd_read, however there is no room for
new data. This would lead to busy loop in alsa thread. To prevent this
scenario, call update_active alsa in snd_pcm_pipewire_poll_revents.

Signed-off-by: Martin Geier <martin.geier@streamunlimited.com>
This commit is contained in:
Martin Geier 2022-10-25 08:23:57 +02:00 committed by Wim Taymans
parent 7182145435
commit 422c2ad726

View file

@ -163,21 +163,15 @@ static int check_active(snd_pcm_ioplug_t *io)
static int update_active(snd_pcm_ioplug_t *io)
{
snd_pcm_pipewire_t *pw = io->private_data;
bool active;
pw->active = check_active(io);
uint64_t val;
active = check_active(io);
if (pw->active)
spa_system_eventfd_write(pw->system, io->poll_fd, 1);
else
spa_system_eventfd_read(pw->system, io->poll_fd, &val);
if (pw->active != active) {
uint64_t val;
pw->active = active;
if (active)
spa_system_eventfd_write(pw->system, io->poll_fd, 1);
else
spa_system_eventfd_read(pw->system, io->poll_fd, &val);
}
return active;
return pw->active;
}
static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw)
@ -233,8 +227,10 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io,
return pw->error;
*revents = pfds[0].revents & ~(POLLIN | POLLOUT);
if (pfds[0].revents & POLLIN && check_active(io))
if (pfds[0].revents & POLLIN && check_active(io)) {
*revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN;
update_active(io);
}
return 0;
}