Skip to content

Commit 24bb27d

Browse files
committed
fix(process): publish child exit before SIGCHLD
Publish the natural-parent notification transaction before delivering the child exit signal so a concurrently scheduled SIGCHLD handler cannot observe the internal Pending phase and miss a nonblocking wait. Snapshot siginfo before autoreap can unhash the child identity, complete autoreap before publishing Done, and retain the unconditional parent wait-queue wake after signal delivery. Assert the notification owner's Zombie-to-Dead transition instead of silently leaving an inconsistent autoreap state. Add a dunitest regression whose SIGCHLD handler immediately reaps the expected child with waitpid(WNOHANG), including bounded observation and failure-safe child and signal-action cleanup. Signed-off-by: longjin <longjin@dragonos.org>
1 parent dae8113 commit 24bb27d

2 files changed

Lines changed: 127 additions & 18 deletions

File tree

kernel/src/process/manager/exit.rs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,11 @@ impl ProcessManager {
216216
)
217217
}
218218

219-
/// Complete the unique natural-parent notification transaction. Signal
220-
/// delivery and an optional owner-authorized autoreap happen while the
221-
/// phase is Pending; Done is then published before the final parent wake.
219+
/// Complete the unique natural-parent notification transaction. Build the
220+
/// child snapshot while identity is stable, perform an optional
221+
/// owner-authorized autoreap, and publish Done before signal delivery. A
222+
/// SIGCHLD handler may call wait immediately, so the signal must never
223+
/// expose the intermediate Pending phase.
222224
pub(crate) fn notify_natural_parent_owned(
223225
child: &Arc<ProcessControlBlock>,
224226
token: NaturalParentNotifyToken,
@@ -239,9 +241,32 @@ impl ProcessManager {
239241
.unwrap_or(false);
240242
let autoreap = exit_signal == Signal::SIGCHLD as i32 && (ignored || no_cldwait);
241243

242-
if exit_signal > 0 && !(autoreap && ignored) {
244+
// Snapshot every field before autoreap can unhash the child's PID.
245+
let notification = if exit_signal > 0 && !(autoreap && ignored) {
243246
let signal = Signal::from(exit_signal);
244-
let mut info = Self::child_exit_siginfo(child, &parent, signal);
247+
Some((signal, Self::child_exit_siginfo(child, &parent, signal)))
248+
} else {
249+
None
250+
};
251+
252+
if autoreap {
253+
let transition = child
254+
.sighand()
255+
.try_reap_natural_child_as_notify_owner(child, &token);
256+
assert_eq!(
257+
transition,
258+
ReapTransition::Reaped,
259+
"natural-parent notification owner failed to autoreap"
260+
);
261+
unsafe { ProcessManager::release(child.raw_pid()) };
262+
}
263+
264+
assert!(
265+
child.sighand().complete_natural_parent_notify(token),
266+
"natural-parent notification ownership changed"
267+
);
268+
269+
if let Some((signal, mut info)) = notification {
245270
if let Err(e) =
246271
signal.send_signal_info_to_pcb(Some(&mut info), parent.clone(), PidType::TGID)
247272
{
@@ -254,19 +279,6 @@ impl ProcessManager {
254279
}
255280
}
256281

257-
if autoreap
258-
&& child
259-
.sighand()
260-
.try_reap_natural_child_as_notify_owner(child, &token)
261-
== ReapTransition::Reaped
262-
{
263-
unsafe { ProcessManager::release(child.raw_pid()) };
264-
}
265-
266-
assert!(
267-
child.sighand().complete_natural_parent_notify(token),
268-
"natural-parent notification ownership changed"
269-
);
270282
ProcessManager::wake_wait_parent(&parent);
271283

272284
if child.is_kthread() {

user/apps/tests/dunitest/suites/normal/process_signal_fork.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,45 @@ class SignalMaskGuard {
112112
bool valid_;
113113
};
114114

115+
class SignalActionGuard {
116+
public:
117+
SignalActionGuard(int signal, const struct sigaction& action) : signal_(signal), valid_(false) {
118+
valid_ = sigaction(signal_, &action, &old_action_) == 0;
119+
}
120+
SignalActionGuard(const SignalActionGuard&) = delete;
121+
SignalActionGuard& operator=(const SignalActionGuard&) = delete;
122+
123+
~SignalActionGuard() {
124+
if (valid_) {
125+
sigaction(signal_, &old_action_, nullptr);
126+
}
127+
}
128+
129+
bool valid() const { return valid_; }
130+
131+
private:
132+
struct sigaction old_action_ {};
133+
int signal_;
134+
bool valid_;
135+
};
136+
137+
volatile sig_atomic_t sigchld_expected_pid = -1;
138+
volatile sig_atomic_t sigchld_wait_result = -2;
139+
volatile sig_atomic_t sigchld_wait_errno = 0;
140+
volatile sig_atomic_t sigchld_wait_status = 0;
141+
volatile sig_atomic_t sigchld_handler_calls = 0;
142+
143+
void ReapExpectedChildFromSigchld(int) {
144+
const int saved_errno = errno;
145+
int status = 0;
146+
const pid_t result = waitpid(static_cast<pid_t>(sigchld_expected_pid), &status, WNOHANG);
147+
sigchld_wait_result = static_cast<sig_atomic_t>(result);
148+
sigchld_wait_errno = result < 0 ? errno : 0;
149+
sigchld_wait_status = status;
150+
++sigchld_handler_calls;
151+
errno = saved_errno;
152+
}
153+
115154
struct LastThreadExitArgs {
116155
int ready_fd;
117156
pid_t leader_tid;
@@ -207,6 +246,64 @@ void RunMultithreadedSignalChild(int ready_fd) {
207246

208247
} // namespace
209248

249+
TEST(ProcessSignalFork, SigchldHandlerCanImmediatelyReapExitedChild) {
250+
struct sigaction action {};
251+
action.sa_handler = ReapExpectedChildFromSigchld;
252+
sigemptyset(&action.sa_mask);
253+
action.sa_flags = SA_RESTART;
254+
SignalActionGuard action_guard(SIGCHLD, action);
255+
ASSERT_TRUE(action_guard.valid()) << "sigaction failed: errno=" << errno << " ("
256+
<< strerror(errno) << ")";
257+
258+
int gate[2] = {-1, -1};
259+
ASSERT_EQ(0, pipe(gate)) << "pipe failed: " << strerror(errno);
260+
261+
const pid_t child = fork();
262+
if (child < 0) {
263+
const int fork_errno = errno;
264+
close(gate[0]);
265+
close(gate[1]);
266+
FAIL() << "fork failed: errno=" << fork_errno << " (" << strerror(fork_errno) << ")";
267+
return;
268+
}
269+
if (child == 0) {
270+
close(gate[1]);
271+
char value = 0;
272+
if (read(gate[0], &value, sizeof(value)) != static_cast<ssize_t>(sizeof(value))) {
273+
_exit(120);
274+
}
275+
_exit(42);
276+
}
277+
ChildProcessGuard child_guard(child);
278+
279+
close(gate[0]);
280+
sigchld_expected_pid = child;
281+
sigchld_wait_result = -2;
282+
sigchld_wait_errno = 0;
283+
sigchld_wait_status = 0;
284+
sigchld_handler_calls = 0;
285+
const char gate_value = 'X';
286+
const ssize_t gate_write = write(gate[1], &gate_value, sizeof(gate_value));
287+
const int gate_errno = errno;
288+
close(gate[1]);
289+
ASSERT_EQ(static_cast<ssize_t>(sizeof(gate_value)), gate_write)
290+
<< "write(gate) failed: errno=" << gate_errno << " (" << strerror(gate_errno) << ")";
291+
292+
for (int i = 0; i < 500 && sigchld_handler_calls == 0; ++i) {
293+
SleepForMillis(1);
294+
}
295+
296+
ASSERT_GT(sigchld_handler_calls, 0) << "SIGCHLD handler was not invoked";
297+
EXPECT_EQ(child, static_cast<pid_t>(sigchld_wait_result))
298+
<< "waitpid(WNOHANG) in SIGCHLD handler did not observe the published exit; errno="
299+
<< sigchld_wait_errno;
300+
if (sigchld_wait_result == child) {
301+
child_guard.Release();
302+
ASSERT_TRUE(WIFEXITED(sigchld_wait_status)) << "child status=" << sigchld_wait_status;
303+
EXPECT_EQ(42, WEXITSTATUS(sigchld_wait_status));
304+
}
305+
}
306+
210307
TEST(ProcessSignalFork, SigchldInfoUsesGroupLeaderWhenLastNonleaderExits) {
211308
const uid_t leader_uid = getuid();
212309
sigset_t blocked;

0 commit comments

Comments
 (0)