Re: [PATCH] [RFC] Add test to detect spurious suspend wakeups
Jan Kiszka <[email protected]>
| Newsgroups | dev.linux.lists.xenomai |
|---|---|
| Message-ID | <[email protected]> |
On 12.05.26 09:17, Richard Weinberger wrote: > If a task is suspend and SIGSHADOW is used to harden it, > it can happen that the target task itself harends (e.g. by using > a syscall). Then the already hardened and suspended task will see > SIGSHADOW and resumes. > > Assisted-by: Claude:claude-opus-4-7 > Signed-off-by: Richard Weinberger <[email protected]> > --- > configure.ac | 1 + > testsuite/smokey/Makefile.am | 3 + > .../smokey/spurious-suspend-wake/Makefile.am | 10 + > .../spurious-suspend-wake.c | 238 ++++++++++++++++++ > 4 files changed, 252 insertions(+) > create mode 100644 testsuite/smokey/spurious-suspend-wake/Makefile.am > create mode 100644 testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c > > diff --git a/configure.ac b/configure.ac > index 0d6dd87b6..69d9947fd 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -1086,6 +1086,7 @@ AC_CONFIG_FILES([ \ > testsuite/smokey/bufp/Makefile \ > testsuite/smokey/sigdebug/Makefile \ > testsuite/smokey/timerfd/Makefile \ > + testsuite/smokey/spurious-suspend-wake/Makefile \ > testsuite/smokey/tsc/Makefile \ > testsuite/smokey/leaks/Makefile \ > testsuite/smokey/memcheck/Makefile \ > diff --git a/testsuite/smokey/Makefile.am b/testsuite/smokey/Makefile.am > index c49a5d77f..f3c1151b6 100644 > --- a/testsuite/smokey/Makefile.am > +++ b/testsuite/smokey/Makefile.am > @@ -37,6 +37,7 @@ COBALT_SUBDIRS = \ > setsched \ > sigdebug \ > timerfd \ > + spurious-suspend-wake \ > tsc \ > xddp \ > y2038 \ > @@ -82,6 +83,7 @@ DIST_SUBDIRS = \ > setsched \ > sigdebug \ > timerfd \ > + spurious-suspend-wake \ > tsc \ > xddp \ > y2038 \ > @@ -130,6 +132,7 @@ smokey_LDADD = \ > $(plugin_list) \ > ../../lib/smokey/libsmokey@[email protected] \ > ../../lib/copperplate/libcopperplate@[email protected] \ > + ../../lib/alchemy/libalchemy@[email protected] \ > @XENO_CORE_LDADD@ \ > @XENO_USER_LDADD@ \ > -lpthread -lrt > diff --git a/testsuite/smokey/spurious-suspend-wake/Makefile.am b/testsuite/smokey/spurious-suspend-wake/Makefile.am > new file mode 100644 > index 000000000..b2f4f3b6c > --- /dev/null > +++ b/testsuite/smokey/spurious-suspend-wake/Makefile.am > @@ -0,0 +1,10 @@ > +noinst_LIBRARIES = libspurious-suspend-wake.a > + > +libspurious_suspend_wake_a_SOURCES = spurious-suspend-wake.c > + > +CCLD = $(top_srcdir)/scripts/wrap-link.sh $(CC) > + > +libspurious_suspend_wake_a_CPPFLAGS = \ > + @XENO_USER_CFLAGS@ \ > + -I$(top_srcdir)/include > + > diff --git a/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c b/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c > new file mode 100644 > index 000000000..969bd5344 > --- /dev/null > +++ b/testsuite/smokey/spurious-suspend-wake/spurious-suspend-wake.c > @@ -0,0 +1,238 @@ > +#include <stdio.h> > +#include <stdlib.h> > +#include <stdbool.h> > +#include <unistd.h> > +#include <errno.h> > +#include <string.h> > +#include <sched.h> > +#include <sys/types.h> > +#include <sys/syscall.h> > +#include <smokey/smokey.h> > +#include <alchemy/task.h> > + > +smokey_test_plugin(spurious_suspend_wake, > + SMOKEY_ARGLIST( > + SMOKEY_INT(iters), > + SMOKEY_INT(settle_us), > + SMOKEY_INT(quiet_us), > + SMOKEY_INT(victim_cpu), > + SMOKEY_INT(suspender_cpu), > + ), > + "Detect SIGSHADOW vs xnthread_suspend race that " > + "spuriously resumes an rt_task_suspend()'d task."); > + > +static volatile unsigned long victim_counter; > +static volatile bool stop; > +static RT_TASK victim_task; > +static RT_TASK suspender_task; > + > +static int iters = 5000; > +static int settle_us = 100; > +static int quiet_us = 200; > +static int victim_cpu = 0; > +static int suspender_cpu = 1; > + > +static unsigned int spurious; > +static unsigned long max_delta; > +static int cycles_done; > + > +static void victim_body(void *arg) > +{ > + (void)arg; > + > + /* > + * Tight relax/harden bouncing: > + * - syscall(getpid) is a raw Linux syscall, sets XNRELAX on > + * return, leaving us in secondary mode. > + * - rt_task_sleep() resolves via libcobalt as a primary-mode > + * syscall, so the wrapper runs xnthread_harden() before the > + * sleep proper. On return we are back in primary mode. > + * > + * The window between the two is when the deferred SIGSHADOW > + * HARDEN posted by a concurrent rt_task_suspend() can land on a > + * non-XNRELAX victim and trigger the spurious wake. > + */ > + while (!stop) { > + syscall(__NR_getpid); > + rt_task_sleep(1000); /* 1us, also forces a harden */ > + __atomic_add_fetch(&victim_counter, 1, __ATOMIC_RELAXED); > + } > +} > + > +static void suspender_body(void *arg) > +{ > + int i, ret; > + > + (void)arg; > + > + for (i = 0; i < iters && !stop; i++) { > + unsigned long c1, c2, delta; > + > + ret = rt_task_suspend(&victim_task); > + if (ret) { > + smokey_warning("rt_task_suspend at iter %d: %s", > + i, symerror(ret)); > + break; > + } > + > + /* > + * Let SIGSHADOW HARDEN actually fire so the victim > + * either properly blocks on XNSUSP or, if the race > + * fired, has already been spuriously resumed. > + */ > + rt_task_sleep((RTIME)settle_us * 1000); > + > + c1 = __atomic_load_n(&victim_counter, __ATOMIC_RELAXED); > + > + /* Quiet window: victim must NOT advance. */ > + rt_task_sleep((RTIME)quiet_us * 1000); > + > + c2 = __atomic_load_n(&victim_counter, __ATOMIC_RELAXED); > + > + ret = rt_task_resume(&victim_task); > + if (ret) { > + smokey_warning("rt_task_resume at iter %d: %s", > + i, symerror(ret)); > + break; > + } > + > + delta = c2 - c1; > + if (delta != 0) { > + spurious++; > + if (delta > max_delta) > + max_delta = delta; > + if (spurious <= 5) > + smokey_trace("iter %d: victim advanced by " > + "%lu in quiet window", i, delta); > + } > + > + /* Let the victim accumulate forward progress between > + * cycles, otherwise we hammer it continuously and can no > + * longer tell forward progress from wake. */ > + rt_task_sleep((RTIME)quiet_us * 1000); > + } > + > + cycles_done = i; > +} > + > +static int run_spurious_suspend_wake(struct smokey_test *t, int argc, > + char *const argv[]) > +{ > + cpu_set_t set; > + int ret, ncpu; > + > + smokey_parse_args(t, argc, argv); > + if (SMOKEY_ARG_ISSET(spurious_suspend_wake, iters)) > + iters = SMOKEY_ARG_INT(spurious_suspend_wake, iters); > + if (SMOKEY_ARG_ISSET(spurious_suspend_wake, settle_us)) > + settle_us = SMOKEY_ARG_INT(spurious_suspend_wake, settle_us); > + if (SMOKEY_ARG_ISSET(spurious_suspend_wake, quiet_us)) > + quiet_us = SMOKEY_ARG_INT(spurious_suspend_wake, quiet_us); > + if (SMOKEY_ARG_ISSET(spurious_suspend_wake, victim_cpu)) > + victim_cpu = SMOKEY_ARG_INT(spurious_suspend_wake, victim_cpu); > + if (SMOKEY_ARG_ISSET(spurious_suspend_wake, suspender_cpu)) > + suspender_cpu = SMOKEY_ARG_INT(spurious_suspend_wake, > + suspender_cpu); Do we really need all these options? Affinity tuning could rather be done within the set of CPUs the test is given. > + > + ncpu = sysconf(_SC_NPROCESSORS_ONLN); > + if (ncpu < 2) { See above: You should better check for size of the affinity mask the test has been given. > + smokey_note("need >= 2 online CPUs to drive the race"); > + return -ENOSYS; Let's warn and skip but not fail. > + } > + if (victim_cpu < 0 || suspender_cpu < 0 || > + victim_cpu >= ncpu || suspender_cpu >= ncpu || > + victim_cpu == suspender_cpu) { > + smokey_warning("victim_cpu and suspender_cpu must be " > + "distinct values in [0, %d)", ncpu); > + return -EINVAL; > + } > + if (iters <= 0 || settle_us <= 0 || quiet_us <= 0) { > + smokey_warning("iters, settle_us, quiet_us must be positive"); > + return -EINVAL; > + } > + > + stop = false; > + victim_counter = 0; > + spurious = 0; > + max_delta = 0; > + cycles_done = 0; > + > + ret = rt_task_create(&victim_task, "ssw-victim", 0, 50, T_JOINABLE); > + if (ret) { > + smokey_warning("rt_task_create(victim): %s", symerror(ret)); > + return ret; > + } > + > + CPU_ZERO(&set); > + CPU_SET(victim_cpu, &set); > + ret = rt_task_set_affinity(&victim_task, &set); > + if (ret) { > + smokey_warning("rt_task_set_affinity(victim, %d): %s", > + victim_cpu, symerror(ret)); > + goto fail_after_victim_create; > + } > + > + ret = rt_task_create(&suspender_task, "ssw-suspender", 0, 50, > + T_JOINABLE); > + if (ret) { > + smokey_warning("rt_task_create(suspender): %s", symerror(ret)); > + goto fail_after_victim_create; > + } > + > + CPU_ZERO(&set); > + CPU_SET(suspender_cpu, &set); > + ret = rt_task_set_affinity(&suspender_task, &set); > + if (ret) { > + smokey_warning("rt_task_set_affinity(suspender, %d): %s", > + suspender_cpu, symerror(ret)); > + goto fail_after_suspender_create; > + } > + > + ret = rt_task_start(&victim_task, victim_body, NULL); > + if (ret) { > + smokey_warning("rt_task_start(victim): %s", symerror(ret)); > + goto fail_after_suspender_create; > + } > + > + smokey_trace("victim on CPU%d, suspender on CPU%d, %d iters, " > + "settle=%dus, quiet=%dus", > + victim_cpu, suspender_cpu, iters, settle_us, quiet_us); > + > + ret = rt_task_start(&suspender_task, suspender_body, NULL); > + if (ret) { > + smokey_warning("rt_task_start(suspender): %s", symerror(ret)); > + stop = true; > + rt_task_join(&victim_task); > + goto fail_after_suspender_create; > + } > + > + rt_task_join(&suspender_task); > + > + stop = true; > + /* > + * Belt and braces: the suspender may have left the victim > + * suspended if it broke out early. rt_task_resume is a no-op on > + * a non-suspended task, so this is always safe. > + */ > + rt_task_resume(&victim_task); > + rt_task_join(&victim_task); > + > + rt_task_delete(&suspender_task); > + rt_task_delete(&victim_task); > + > + if (spurious) { > + smokey_warning("FAIL: %u/%d cycles saw the victim advance " > + "during the quiet window (max delta %lu)", > + spurious, cycles_done, max_delta); > + return -EPROTO; > + } > + > + smokey_trace("no spurious wakes in %d cycles", cycles_done); > + return 0; > + > +fail_after_suspender_create: > + rt_task_delete(&suspender_task); > +fail_after_victim_create: > + rt_task_delete(&victim_task); > + return ret; > +} Would love to add it to our testsuite, but maybe rather as part of the alchemy tests. Jan -- Siemens AG, Foundational Technologies Linux Expert Center