[igt-dev] [PATCH i-g-t v2 1/6] tests/core_hotunplug: properly finish processes using audio devices
Andi Shyti
andi.shyti at linux.intel.com
Thu May 5 15:54:09 UTC 2022
Hi Mauro,
[...]
> +static void pulseaudio_unload_module(proc_t *proc_info)
> +{
> + char xdg_dir[PATH_MAX];
> + const char *homedir;
> + struct passwd *pw;
> +
> + igt_fork(child, 1) {
> + pw = getpwuid(proc_info->euid);
> + homedir = pw->pw_dir;
> + snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", proc_info->euid);
> +
> + igt_info("Ask pulseaudio to stop using audio device\n");
> +
> + setgid(proc_info->egid);
> + setuid(proc_info->euid);
> + clearenv();
> + setenv("HOME", homedir, 1);
> + setenv("XDG_RUNTIME_DIR",xdg_dir, 1);
> +
> + exit(system("for i in $(pacmd list-sources|grep module:|cut -d : -f 2); do pactl unload-module $i; done"));
you shouldn't need for the exit()
> + }
do we need for igt_waitchildren() at the end?
> +}
> +
> +static int
> +__igt_lsof_kill_proc(proc_t *proc_info, char *proc_path)
> +{
> + const char *audio_dev = "/dev/snd/";
> + char path[PATH_MAX * 2];
> + struct dirent *d;
> + struct stat st;
> + char *fd_lnk;
> + int fail = 0;
> + ssize_t read;
nitpick: shall we move some of the variables above inside the
while loop?
> + DIR *dp = opendir(proc_path);
> + igt_assert(dp);
> +
> + while ((d = readdir(dp))) {
> + if (*d->d_name == '.')
> + continue;
do we need to consider ".." as well? "by-path"?
> + memset(path, 0, sizeof(path));
> + snprintf(path, sizeof(path), "%s/%s", proc_path, d->d_name);
> +
> + if (lstat(path, &st) == -1)
> + continue;
should this be treated as an unlikely error?
> + fd_lnk = malloc(st.st_size + 1);
> +
> + igt_assert((read = readlink(path, fd_lnk, st.st_size + 1)));
> + fd_lnk[read] = '\0';
are we sure that in /dev/snd we have only links? In my PC I have
only character interfaces.
> + if (strncmp(audio_dev, fd_lnk, strlen(audio_dev))) {
> + free(fd_lnk);
> + continue;
> + }
> +
> + free(fd_lnk);
> +
> + /*
> + * In order to avoid racing against pa/systemd, ensure that
> + * pulseaudio will close all audio files. This should be
> + * enough to unbind audio modules and won't case race issues
/case/cause/
> + * with systemd trying to reload it.
> + */
> + if (!strcmp(proc_info->cmd, "pulseaudio")) {
> + pulseaudio_unload_module(proc_info);
> + break;
> + }
> +
> + /*
> + * FIXME: terminating pipewire-pulse is not that easy, as
> + * pipewire there's no standard way up to pipewire version
> + * 0.3.49. Just trying to kill pipewire will start a race
> + * between IGT and systemd. If IGT wins, the audio driver
> + * will be unloaded before systemd tries to reload it, but
> + * if systemd wins, the audio device will be re-opened and
> + * used before IGT has a chance to remove the audio driver.
> + * Pipewire version 0.3.50 should bring a standard way:
> + *
> + * 1) start a thread running:
> + * pw-reserve -n Audio0 -r
> + * 2) unload/unbind the the audio driver(s);
> + * 3) stop the pw-reserve thread.
> + *
> + * We should add support for it once distros start shipping it.
> + */
thanks for the explanation!
> + /* For all other processes, just kill them */
> + igt_info("process %d (%s) is using audio device. Should be terminated.\n",
> + proc_info->tid, proc_info->cmd);
> +
> + if (kill(proc_info->tid, SIGTERM) < 0) {
> + igt_info("Fail to terminate %s (pid: %d) with SIGTERM\n",
> + proc_info->cmd, proc_info->tid);
> + if (kill(proc_info->tid, SIGABRT) < 0) {
> + fail++;
> + igt_info("Fail to terminate %s (pid: %d) with SIGABRT\n",
> + proc_info->cmd, proc_info->tid);
> + }
> + }
> +
> + break;
> + }
> +
> + closedir(dp);
> + return fail;
> +}
> +
> +/*
> + * This function identifies each process running on the machine that is
> + * opening an audio device and tries to stop it.
> + *
> + * Special care should be taken with pipewire and pipewire-pulse, as those
> + * daemons are respanned if they got killed.
> + */
> +int
> +igt_lsof_kill_audio_processes(void)
> +{
> + char path[PATH_MAX];
> + proc_t *proc_info;
> + PROCTAB *proc;
> + int fail = 0;
> +
> + proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
you could eventually make an igt_openproc() with the assert as
this is a pattern that it's repeated several times. Up to you.
> + igt_assert(proc != NULL);
> +
> + while ((proc_info = readproc(proc, NULL))) {
> + if (snprintf(path, sizeof(path), "/proc/%d/fd", proc_info->tid) < 1) {
> + fail++;
we are counting failures but using it more as a boolean. Is there
any use of fail?
> + } else {
> + fail += __igt_lsof_kill_proc(proc_info, path);
> + }
brackets not needed here
> + freeproc(proc_info);
> + }
> + closeproc(proc);
> +
> + return fail;
> +}
> +
> static struct igt_siglatency {
> timer_t timer;
> struct timespec target;
> diff --git a/lib/igt_aux.h b/lib/igt_aux.h
> index 9f2588aeca90..bb96d1afb777 100644
> --- a/lib/igt_aux.h
> +++ b/lib/igt_aux.h
> @@ -306,6 +306,7 @@ bool igt_allow_unlimited_files(void);
> int igt_is_process_running(const char *comm);
> int igt_terminate_process(int sig, const char *comm);
> void igt_lsof(const char *dpath);
> +int igt_lsof_kill_audio_processes(void);
>
> #define igt_hweight(x) \
> __builtin_choose_expr(sizeof(x) == 8, \
> diff --git a/lib/igt_kmod.c b/lib/igt_kmod.c
> index f252535d5a3a..87a59245f699 100644
> --- a/lib/igt_kmod.c
> +++ b/lib/igt_kmod.c
> @@ -389,7 +389,7 @@ igt_i915_driver_load(const char *opts)
> return 0;
> }
>
> -int __igt_i915_driver_unload(const char **who)
> +int igt_audio_driver_unload(const char **who)
> {
> int ret;
> const char *sound[] = {
> @@ -398,6 +398,27 @@ int __igt_i915_driver_unload(const char **who)
> NULL,
> };
>
> + for (const char **m = sound; *m; m++) {
> + if (igt_kmod_is_loaded(*m)) {
little insignificant nit: with
if (!igt_kmod_is_loaded(*m))
continue;
you save one level of indentation.
> + if (igt_lsof_kill_audio_processes())
> + return 1;
mmhhh... I don't like the return 1 because makes this function a
hybrid boolean/error function and we can't rely anyomore on the
return value. Please identify a proper errno to return.
> +
> + kick_snd_hda_intel();
> + ret = igt_kmod_unload(*m, 0);
> + if (ret) {
> + if (who)
> + *who = *m;
> + return ret;
> + }
> + }
> + }
> + return 0;
> +}
[...]
> @@ -152,19 +152,14 @@ static void driver_unbind(struct hotunplug *priv, const char *prefix,
> * safest and easiest way out.
> */
> if (priv->snd_unload) {
> - igt_terminate_process(SIGTERM, "alsactl");
> -
> - /* unbind snd_hda_intel */
> - kick_snd_hda_intel();
> -
> - if (igt_kmod_unload("snd_hda_intel", 0)) {
> + if (igt_audio_driver_unload(NULL)) {
why give NULL insteadd of a proper string? We can provide a
better log.
> priv->snd_unload = false;
> - igt_warn("Could not unload snd_hda_intel\n");
> + igt_warn("Could not unload audio driver\n");
> igt_kmod_list_loaded();
> igt_lsof("/dev/snd");
> igt_skip("Audio is in use, skipping\n");
> } else {
> - igt_info("Preventively unloaded snd_hda_intel\n");
> + igt_info("Preventively unloaded audio driver\n");
> }
> }
Andi
More information about the igt-dev
mailing list