[PATCH v4 1/3] perf sched stats: Fix SIGCHLD vs pause() race in schedstat_record()

From: Swapnil Sapkal

Date: Wed May 20 2026 - 06:28:26 EST


If the profiled workload exits very quickly, SIGCHLD can be delivered
and consumed by the empty signal handler before the process enters
pause(), causing an indefinite hang.

Fix this with a simpler approach:

- The signal handler now sets a 'volatile sig_atomic_t done' flag.
Reset 'done' before registering signal handlers so that an early
signal during setup is not discarded by a later reset.

- Replace pause() with a loop that checks 'done' and uses
waitpid(WNOHANG) to detect child exit without blocking. This
handles both workload mode (child exits) and system-wide mode
(user sends SIGINT/SIGTERM). Using WNOHANG avoids the SA_RESTART
problem where a blocking waitpid() would auto-restart and ignore
the done flag if the child doesn't exit on signal.

Suggested-by: Namhyung Kim <namhyung@xxxxxxxxxx>
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Swapnil Sapkal <swapnil.sapkal@xxxxxxx>
---
tools/perf/builtin-sched.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 53a93aa18853..7da71c372e25 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -36,6 +36,7 @@
#include <linux/zalloc.h>
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/wait.h>
#include <inttypes.h>

#include <errno.h>
@@ -3758,8 +3759,11 @@ static int process_synthesized_schedstat_event(const struct perf_tool *tool,
return 0;
}

+static volatile sig_atomic_t done;
+
static void sighandler(int sig __maybe_unused)
{
+ done = 1;
}

static int enable_sched_schedstats(int *reset)
@@ -3819,6 +3823,7 @@ static int perf_sched__schedstat_record(struct perf_sched *sched,
.mode = PERF_DATA_MODE_WRITE,
};

+ done = 0;
signal(SIGINT, sighandler);
signal(SIGCHLD, sighandler);
signal(SIGTERM, sighandler);
@@ -3903,8 +3908,11 @@ static int perf_sched__schedstat_record(struct perf_sched *sched,
if (argc)
evlist__start_workload(evlist);

- /* wait for signal */
- pause();
+ while (!done) {
+ if (argc && waitpid(evlist->workload.pid, NULL, WNOHANG) > 0)
+ break;
+ sleep(1);
+ }

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