Manage signals and collect process state
Separate notification from accounting, manage thread masks, and reason through fork, exec, exit, and wait.
On this page
The question for today
What you’ll understand
- Distinguish process-wide signal dispositions from per-thread masks and pending delivery.
- Explain why standard signals cannot count child terminations.
- Design a shutdown and child-reaping flow that respects asynchronous handler restrictions.
A supervisor starts several workers and receives one SIGCHLD notification. Does that mean one worker exited? The tempting interpretation confuses a notification channel with a ledger. Linux maintains process state independently of how often your handler runs. Understanding that separation prevents zombie accumulation, missed shutdown requests, and brittle retry logic. We will follow signals through thread selection and then follow a child through creation, program replacement, termination, and collection of its status.
Generation, pending state, and delivery are separate events
A signal is first generated, may remain pending, and is eventually delivered or synchronously accepted according to its kind and the receiving state. For an ordinary standard signal, repeated generation while the same signal is already pending does not build an event counter. With that signal blocked throughout an interval, the pending indicator is either zero or one, even if a hundred requests arrive. This coalescing is appropriate for messages such as “something changed”; it is insufficient for “perform this action exactly one hundred times.” Use an explicit queue, counter with suitable synchronization, or another protocol when multiplicity matters.
The disposition describes the action for a signal and belongs to the process: default action, ignore, or an installed handler. Each thread has its own blocked-signal mask. A process-directed signal may go to an eligible thread that has it unblocked; a thread-directed signal targets a particular thread. Blocking in only the main thread therefore does not establish a process-wide delivery policy. A common controlled design blocks selected signals before creating worker threads, lets the workers inherit that mask, and dedicates one thread to synchronous acceptance. SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
Handlers interrupt ordinary user-space assumptions
An asynchronous handler can interrupt a thread while a library operation has partially updated internal state. Calling a convenient logging routine from the handler may re-enter the same library or encounter a lock held by the interrupted execution. Ordinary thread safety does not imply async-signal safety. Keep handler work within the documented safe interface set and use a minimal notification mechanism with a clearly defined protocol. A volatile sig_atomic_t flag can support a narrow flag-setting pattern in C, but it is not a general replacement for synchronization between threads or a lossless event counter. Preserve errno if the handler modifies it and the interrupted code depends on it.
Synchronous handling with sigwaitinfo or a suitable signalfd arrangement can move application logic into a normal control flow, provided the relevant signals are blocked consistently in the threads that should not receive handlers. Neither design makes delivery instantaneous. A blocked system call interrupted by a handler may restart or report EINTR depending on the interface and flags; check its specific contract. A loop must also track partial progress and its original deadline. Restarting a fresh full-duration timeout after every interruption can exceed the intended shutdown budget indefinitely, even though every individual call appears correct.
Treat child state as the authoritative ledger
Fork creates a new process; in a multithreaded parent, only the calling thread exists in the child. The child inherits signal dispositions and the calling thread’s mask, while its pending signal set begins empty. Before exec in that child, restrictions on safe function calls matter because copied synchronization objects may describe locks whose owning threads no longer exist there. Exec then replaces the program image without creating a new process identity. Caught dispositions reset to defaults, ignored dispositions generally remain ignored, and the signal mask persists. A launcher must intentionally choose the mask and descriptor inheritance expected by the new program rather than assuming exec resets everything.
When a child terminates, ordinary waitable status can remain until the parent collects it with a wait-family operation. That zombie is not still running its former workload; it is retained accounting state. Treat SIGCHLD as a prompt to inspect available statuses, not as one receipt per exit. A supervisor commonly loops over waitpid with WNOHANG until no further eligible status is ready, handling errors and its chosen stop/continue options explicitly. This discussion assumes ordinary child reaping rather than explicit SIG_IGN or SA_NOCLDWAIT policies that change zombie behavior. During shutdown, request termination, stop admitting new work, maintain a deadline, and continue collecting children. Sending a termination request and observing a reaped status are different milestones.
Work it through
One notification, eight exits
A supervisor created 20 children. Eight terminate while SIGCHLD is blocked, none was previously pending, no child is auto-reaped, and the other 12 remain alive. After unblocking, one notification prompts a nonblocking wait loop. How many children must the loop collect?
Repeated generation of the same blocked standard SIGCHLD leaves one pending indicator; eight generations do not imply eight queued deliveries.
The authoritative child records contain eight terminated, waitable children. Repeated successful waitpid calls collect eight statuses; then WNOHANG returns zero while eligible children remain alive but have no requested state change ready.
Account for the original 20 children as 12 alive plus 0 unreaped terminated children plus 8 already reaped children. The single notification has led to eight collections.
Eight statuses must be collected. Counting handler invocations would have undercounted exits by seven.
The living notebook
Explore the model
Interactive lab · queue
Notification is not a reaping capacity plan
Interpret arrival λ as illustrative child exits and service μ as illustrative status collections, in statuses per second. Compare separate fixed-rate runs at λ=4, μ=2 and λ=2, μ=4. Before each: Reset, re-set both rates, predict growth or drainage from four statuses, then Play or Step. The second run starts fresh; editing rates alone retains elapsed time rather than reversing rates on the old backlog.
- Fluid backlog
- 4
- Net rate · λ − μ
- −1 /s
- M/M/1 mean residence
- 1 s
Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forThe fluid formula max(0,4+(λ−μ)t) illustrates accounting pressure. It does not simulate signals, integer zombies, waitpid scheduling, or the finite child cohort; its separate M/M/1 residence-time readout additionally requires its stated stochastic assumptions.
Pause & reason
Check your understanding
Your turn
Try it yourself
A shutdown deadline has monotonic clock reading 15.0 seconds. The wait is interrupted when that same clock reads 12.4 seconds. What remaining budget should the loop use, and why should it still reap children?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Subtract the current clock reading from the original deadline; a shutdown request is not a collected exit status.
Compare with the solution
The remaining budget is 2.6 seconds. Reusing a fresh full timeout would move the effective deadline. Continue collecting ready child statuses during shutdown so completed workers do not leave unreaped accounting records.
Keep the idea
Take it with you
- Standard signal pending state is a notification indicator, not an event count.
- Dispositions are process-wide, masks are per-thread, and inherited state must be chosen deliberately around fork and exec.
- Shutdown requires both lifecycle control and authoritative collection of child status.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.