[PATCH v2 2/3] perf sched stats: Fix SIGCHLD race in schedstat_live()

From: Swapnil Sapkal

Date: Thu Apr 09 2026 - 12:28:17 EST


The same signal race that exists in perf_sched__schedstat_record() also
affects perf_sched__schedstat_live(). A very short-lived workload can
exit and deliver SIGCHLD before pause() is entered, causing an
indefinite hang.

Apply the same fix: block SIGCHLD, SIGINT and SIGTERM after the
workload is forked (evlist__prepare_workload) but before it is started
(evlist__start_workload), then replace pause() with sigsuspend() to
atomically unblock and wait.

Reviewed-by: James Clark <james.clark@xxxxxxxxxx>
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Swapnil Sapkal <swapnil.sapkal@xxxxxxx>
---
tools/perf/builtin-sched.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 3f448acd5046..c716dd33a9c9 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -4653,6 +4653,7 @@ static int perf_sched__schedstat_live(struct perf_sched *sched,
int argc, const char **argv)
{
struct cpu_domain_map **cd_map = NULL;
+ sigset_t sig_mask, oldmask;
struct target target = {};
u32 __maybe_unused md;
struct evlist *evlist;
@@ -4702,11 +4703,17 @@ static int perf_sched__schedstat_live(struct perf_sched *sched,
if (err < 0)
goto out;

+ sigemptyset(&sig_mask);
+ sigaddset(&sig_mask, SIGCHLD);
+ sigaddset(&sig_mask, SIGINT);
+ sigaddset(&sig_mask, SIGTERM);
+ sigprocmask(SIG_BLOCK, &sig_mask, &oldmask);
+
if (argc)
evlist__start_workload(evlist);

- /* wait for signal */
- pause();
+ sigsuspend(&oldmask);
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);

if (reset) {
err = disable_sched_schedstat();
--
2.43.0