[PATCH v2 0/3] perf: Fix SIGCHLD vs pause() race with short-lived workloads
From: Swapnil Sapkal
Date: Thu Apr 09 2026 - 12:26:44 EST
Several perf subcommands (sched stats, lock contention) use the pattern
of forking a workload child, calling evlist__start_workload() to uncork
it, and then calling pause() to wait for a signal (typically SIGCHLD
when the child exits, or SIGINT/SIGTERM from the user).
This pattern has a race condition: if the workload is very short-lived,
the child can exit and deliver SIGCHLD in the window between
evlist__start_workload() and pause(). Since pause() only returns when a
signal is received *while the process is suspended*, and SIGCHLD has
already been delivered and handled by the empty sighandler(), pause()
blocks indefinitely.
The fix uses the standard POSIX pattern for this class of bug:
1. Block SIGCHLD, SIGINT, and SIGTERM (via sigprocmask) after
evlist__prepare_workload() returns but before
evlist__start_workload(). Blocking after the fork ensures the
child workload does not inherit a modified signal mask.
2. Replace pause() with sigsuspend(&oldmask), which atomically
restores the original mask and suspends the process. There is no
window where any signal can slip through unnoticed.
3. Restore the original signal mask after sigsuspend() returns.
All three signals are blocked (not just SIGCHLD) so that an early
Ctrl+C during the remaining setup before sigsuspend() cannot be
consumed and lost, which would cause a hang in system-wide mode where
no SIGCHLD would follow.
Three call sites are affected across two files:
- perf_sched__schedstat_record() in builtin-sched.c
- perf_sched__schedstat_live() in builtin-sched.c
- __cmd_contention() in builtin-lock.c
The two pause() sites in builtin-kwork.c are NOT affected because they
do not register SIGCHLD or fork workload children; they only wait for
user-initiated SIGINT/SIGTERM.
Changes since v1:
- Moved sigprocmask() to after evlist__prepare_workload() so the
forked child does not inherit a blocked SIGCHLD mask, which would
break workloads relying on SIGCHLD (e.g., Node.js, Python asyncio).
(Sashiko review)
- Block SIGINT and SIGTERM in addition to SIGCHLD to prevent an
early Ctrl+C during setup from being consumed before sigsuspend().
- Error paths before sigprocmask no longer need mask restoration
since the mask is not yet modified at that point.
(Sashiko review)
Swapnil Sapkal (3):
perf sched stats: Fix SIGCHLD race in schedstat_record()
perf sched stats: Fix SIGCHLD race in schedstat_live()
perf lock contention: Fix SIGCHLD race in __cmd_contention()
tools/perf/builtin-lock.c | 18 ++++++++++++++++--
tools/perf/builtin-sched.c | 30 ++++++++++++++++++++++++++----
2 files changed, 42 insertions(+), 6 deletions(-)
--
2.43.0