module/alsa: don’t re-create the /dev/snd inotify watcher after each connect failure

When e.g. a USB soundcard is inserted, we get several CREATE
events. In my experiments, we only succeed in connecting to ALSA after
the last event.

This means, we’ll have several CREATE events that we receive, remove
the watcher, attempt to connect, fail, and then re-add the watcher.

What if that “last” CREATE event occurs while our watcher has been
removed? That’s right, we miss it, and will get stuck waiting forever.

The solution is keep the watcher around.

Now, if we’ve been successfully connected to ALSA for a long time,
chances are we’ve built up events (for other cards, for example). We
don’t want to trigger a storm of re-connect attempts, so drain the
event queue after having been disconnected from ALSA.

There *is* a small race here - if a card is removed and
re-added *very* fast, we _may_ accidentally drain the CREATE event. I
don’t see this happening in reality though.
This commit is contained in:
Daniel Eklöf 2021-08-21 11:08:45 +02:00
parent 5af070ee1d
commit 360e1fbada
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

View file

@ -318,38 +318,64 @@ run(struct module *mod)
{ {
int ret = 1; int ret = 1;
int wd = -1; int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
int ifd = inotify_init();
if (ifd < 0) { if (ifd < 0) {
LOG_ERRNO("failed to inotify"); LOG_ERRNO("failed to inotify");
return 1; return 1;
} }
int wd = inotify_add_watch(ifd, "/dev/snd", IN_CREATE);
if (wd < 0) {
LOG_ERRNO("failed to create inotify watcher for /dev/snd");
close(ifd);
return 1;
}
while (true) { while (true) {
enum run_state state = run_while_online(mod); enum run_state state = run_while_online(mod);
switch (state) { switch (state) {
case RUN_DONE: case RUN_DONE:
ret = 0; ret = 0;
break; goto out;
case RUN_ERROR: case RUN_ERROR:
ret = 1; ret = 1;
break; goto out;
case RUN_FAILED_CONNECT: case RUN_FAILED_CONNECT:
break;
case RUN_DISCONNECTED: case RUN_DISCONNECTED:
break; /*
} * Weve been connected - drain the watcher
*
wd = inotify_add_watch(ifd, "/dev/snd", IN_CREATE); * We dont want old, un-releated events (for other
if (wd < 0) { * soundcards, for example) to trigger a storm of
LOG_ERRNO("failed to create inotify watcher for /dev/snd"); * re-connect attempts.
ret = 1; */
break;
}
while (true) { while (true) {
uint8_t buf[1024];
ssize_t amount = read(ifd, buf, sizeof(buf));
if (amount < 0) {
if (errno == EAGAIN)
break;
LOG_ERRNO("failed to drain inotify watcher");
ret = 1;
goto out;
}
if (amount == 0)
break;
}
break;
}
bool have_create_event = false;
while (!have_create_event) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN},
{.fd = ifd, .events = POLLIN}}; {.fd = ifd, .events = POLLIN}};
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
@ -363,42 +389,45 @@ run(struct module *mod)
goto out; goto out;
} }
if (fds[0].revents & POLLIN) { if (fds[0].revents & (POLLIN | POLLHUP)) {
ret = 0; ret = 0;
goto out; goto out;
} }
if (fds[1].revents & POLLIN) { if (fds[1].revents & POLLHUP) {
LOG_ERR("inotify socket closed");
ret = 1;
goto out;
}
assert(fds[1].revents & POLLIN);
while (true) {
char buf[1024]; char buf[1024];
ssize_t len = read(ifd, buf, sizeof(buf)); ssize_t len = read(ifd, buf, sizeof(buf));
if (len < 0) { if (len < 0) {
if (errno == EAGAIN)
break;
LOG_ERRNO("failed to read inotify events"); LOG_ERRNO("failed to read inotify events");
ret = 1; ret = 1;
goto out; goto out;
} }
if (len == 0) { if (len == 0)
LOG_ERR("inotify FD closed"); break;
ret = 1;
goto out;
}
/* Consume inotify data */ /* Consume inotify data */
bool have_create_event = false;
for (const char *ptr = buf; ptr < buf + len; ) { for (const char *ptr = buf; ptr < buf + len; ) {
const struct inotify_event *e = (const struct inotify_event *)ptr; const struct inotify_event *e = (const struct inotify_event *)ptr;
if (e->mask & IN_CREATE) { if (e->mask & IN_CREATE) {
LOG_DBG("inotify: CREATED: /dev/snd/%.*s", e->len, e->name); LOG_DBG("inotify: CREATED: /dev/snd/%.*s", e->len, e->name);
have_create_event = true; have_create_event = true;
} }
ptr += sizeof(*e) + e->len;
}
if (have_create_event) { ptr += sizeof(*e) + e->len;
inotify_rm_watch(ifd, wd);
wd = -1;
break;
} }
} }
} }