This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 55017ee9d41679ac0b512d96569ab417485d39b9 Author: Marco Casaroli <[email protected]> AuthorDate: Tue Oct 20 10:41:46 2026 +0200 sched/sched: Wait for all children when the child status is not kept. With SA_NOCLDWAIT (or SIGCHLD set to SIG_IGN), waitpid() and waitid() returned the PID and the exit status of the first child that exited. XSI says that the children do not become zombies, so the call blocks until all children of the process exit and then fails with ECHILD. A wait for a specific child used nxsched_get_tcb(pid) to see if the child still ran. A child that sent SIGCHLD can still be found during its exit, so a retry after the signal could wait for ever. Add group_has_child(): it finds a running child of a task group (any child or one PID) and skips the threads that are in exit processing. Without retention, waitpid() and waitid() now retry after each SIGCHLD until no child is left, then fail with ECHILD. The wait loops read GROUP_FLAG_NOCLDWAIT again after each signal. Another thread can set SA_NOCLDWAIT while the wait blocks, and that frees the child status structures. The loops then found no structure for the waited-for child (DEBUGASSERT, then a NULL pointer access). Now a wait for a child without a child status structure continues while the child runs and then fails with ECHILD. This also applies when another thread already took the status of the same child. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- sched/group/group.h | 1 + sched/group/group_childstatus.c | 61 +++++++++++++++++++++++++++++++++++++++++ sched/sched/sched_waitid.c | 60 +++++++++++++++++++++++----------------- sched/sched/sched_waitpid.c | 53 +++++++++++++++++++++-------------- 4 files changed, 129 insertions(+), 46 deletions(-) diff --git a/sched/group/group.h b/sched/group/group.h index d2130b1ac8d..4a639edc59f 100644 --- a/sched/group/group.h +++ b/sched/group/group.h @@ -108,6 +108,7 @@ FAR struct child_status_s *group_find_child(FAR struct task_group_s *group, FAR struct child_status_s *group_remove_child(FAR struct task_group_s *group, pid_t pid); void group_remove_children(FAR struct task_group_s *group); +bool group_has_child(FAR struct task_group_s *group, pid_t pid); #endif /* CONFIG_SCHED_CHILD_STATUS */ #endif /* CONFIG_SCHED_HAVE_PARENT */ diff --git a/sched/group/group_childstatus.c b/sched/group/group_childstatus.c index b08940e6e45..c71556c70b1 100644 --- a/sched/group/group_childstatus.c +++ b/sched/group/group_childstatus.c @@ -61,6 +61,15 @@ struct child_pool_s FAR struct child_status_s *freelist; }; +/* The search state of group_has_child() */ + +struct child_search_s +{ + FAR struct task_group_s *group; + pid_t pid; + bool found; +}; + /**************************************************************************** * Private Data ****************************************************************************/ @@ -107,6 +116,28 @@ static void group_dump_children(FAR struct task_group_s *group, # define group_dump_children(t,m) #endif +/**************************************************************************** + * Name: group_child_handler + * + * Description: + * The nxsched_foreach() handler of group_has_child(). + * + ****************************************************************************/ + +static void group_child_handler(FAR struct tcb_s *tcb, FAR void *arg) +{ + FAR struct child_search_s *search = arg; + FAR struct task_group_s *group = tcb->group; + + if (group != NULL && group != search->group && + group->tg_ppid == search->group->tg_pid && + (search->pid == INVALID_PROCESS_ID || group->tg_pid == search->pid) && + (tcb->flags & TCB_FLAG_EXIT_PROCESSING) == 0) + { + search->found = true; + } +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -450,4 +481,34 @@ void group_remove_children(FAR struct task_group_s *group) group_dump_children(group, "group_remove_children"); } +/**************************************************************************** + * Name: group_has_child + * + * Description: + * Check if a child of the task group is still running. This also finds + * the children that have no child status structure. + * + * Input Parameters: + * group - The parent task group. + * pid - The PID of the child, or INVALID_PROCESS_ID for any child. + * + * Returned Value: + * True if the child (or any child) has a thread that is not exiting. + * + * Assumptions: + * Called from a critical section. + * + ****************************************************************************/ + +bool group_has_child(FAR struct task_group_s *group, pid_t pid) +{ + struct child_search_s search; + + search.group = group; + search.pid = pid; + search.found = false; + nxsched_foreach(group_child_handler, &search); + return search.found; +} + #endif /* CONFIG_SCHED_HAVE_PARENT && CONFIG_SCHED_CHILD_STATUS */ diff --git a/sched/sched/sched_waitid.c b/sched/sched/sched_waitid.c index 0fb082d516d..d68a038baa7 100644 --- a/sched/sched/sched_waitid.c +++ b/sched/sched/sched_waitid.c @@ -110,13 +110,16 @@ int waittcb(FAR struct tcb_s *rtcb, idtype_t idtype, int options, while (errcode == OK) { #ifdef CONFIG_SCHED_CHILD_STATUS + /* Another thread can change the action for SIGCHLD while we wait */ + + retains = ((rtcb->group->tg_flags & GROUP_FLAG_NOCLDWAIT) == 0); + /* Check if the task has already died. Signals are not queued in * NuttX. So a possibility is that the child has died and we * missed the death of child signal (we got some other signal * instead). */ - DEBUGASSERT(!retains || rtcb->group->tg_children); if (idtype == P_ALL) { /* We are waiting for any child to exit */ @@ -130,20 +133,27 @@ int waittcb(FAR struct tcb_s *rtcb, idtype_t idtype, int options, exited_child(rtcb, child, info, options); break; } + + /* Without an exit status to wait for (XSI), wait until all + * children exit + */ + + if ((!retains || rtcb->group->tg_children == NULL) && + !group_has_child(rtcb->group, INVALID_PROCESS_ID)) + { + errcode = ECHILD; + break; + } } /* We are waiting for a specific PID. Does this task retain child * status? */ - else if (retains) + else if (retains && + (child = group_find_child(rtcb->group, id)) != NULL) { - /* Yes ... Get the current status of the child task. */ - - child = group_find_child(rtcb->group, (pid_t)id); - DEBUGASSERT(child); - - /* Did the child exit? */ + /* Yes ... Did the child exit? */ if ((child->ch_flags & CHILD_FLAG_EXITED) != 0) { @@ -155,22 +165,14 @@ int waittcb(FAR struct tcb_s *rtcb, idtype_t idtype, int options, break; } } - else + else if (!group_has_child(rtcb->group, id)) { - /* Is that task still alive? */ - - if (nxsched_get_tcb((pid_t)id) == NULL) - { - /* It is no longer running. We know that the child task - * was running okay when we started, so we must have lost - * the signal. In this case, we know that the task exit'ed, - * but we do not know its exit status. It would be better - * to reported ECHILD than bogus status. - */ + /* It is no longer running and there is no exit status (XSI, or + * another thread took it) + */ - errcode = ECHILD; - break; - } + errcode = ECHILD; + break; } #else /* Check if the task has already died. Signals are not queued in @@ -221,6 +223,15 @@ int waittcb(FAR struct tcb_s *rtcb, idtype_t idtype, int options, if (info->si_signo == SIGCHLD) { +#ifdef CONFIG_SCHED_CHILD_STATUS + if (!retains && idtype != P_PGID) + { + /* No exit status (XSI): check the children again */ + + continue; + } +#endif + /* Yes.. Are we waiting for the death of a specific child? */ if (idtype == P_PID) @@ -235,9 +246,8 @@ int waittcb(FAR struct tcb_s *rtcb, idtype_t idtype, int options, if (retains) { child = group_find_child(rtcb->group, info->si_pid); - DEBUGASSERT(child); - - if ((child->ch_flags & CHILD_FLAG_EXITED) != 0) + if (child != NULL && + (child->ch_flags & CHILD_FLAG_EXITED) != 0) { exited_child(rtcb, child, NULL, options); } diff --git a/sched/sched/sched_waitpid.c b/sched/sched/sched_waitpid.c index 9b5a2cb1db0..9c8f877e5e9 100644 --- a/sched/sched/sched_waitpid.c +++ b/sched/sched/sched_waitpid.c @@ -65,6 +65,10 @@ int waittcb(FAR struct tcb_s *rtcb, pid_t pid, int options, while (ret >= 0) { #ifdef CONFIG_SCHED_CHILD_STATUS + /* Another thread can change the action for SIGCHLD while we wait */ + + retains = ((rtcb->group->tg_flags & GROUP_FLAG_NOCLDWAIT) == 0); + /* Check if the task has already died. Signals are not queued in * NuttX. So a possibility is that the child has died and we * missed the death of child signal (we got some other signal @@ -77,7 +81,6 @@ int waittcb(FAR struct tcb_s *rtcb, pid_t pid, int options, * children. */ - DEBUGASSERT(!retains || rtcb->group->tg_children); if (retains && (child = group_exit_child(rtcb->group)) != NULL) { /* A child has exited. Apparently we missed the signal. @@ -103,19 +106,26 @@ int waittcb(FAR struct tcb_s *rtcb, pid_t pid, int options, break; } + + /* Without an exit status to wait for (XSI), wait until all + * children exit + */ + + if ((!retains || rtcb->group->tg_children == NULL) && + !group_has_child(rtcb->group, pid)) + { + ret = -ECHILD; + break; + } } /* We are waiting for a specific PID. Does this task retain child * status? */ - else if (retains) + else if (retains && + (child = group_find_child(rtcb->group, pid)) != NULL) { - /* Get the current status of the child task. */ - - child = group_find_child(rtcb->group, pid); - DEBUGASSERT(child); - /* Did the child exit? */ if ((child->ch_flags & CHILD_FLAG_EXITED) != 0) @@ -139,22 +149,14 @@ int waittcb(FAR struct tcb_s *rtcb, pid_t pid, int options, break; } } - else + else if (!group_has_child(rtcb->group, pid)) { - /* Is that task still alive? */ - - if (nxsched_get_tcb(pid) == NULL) - { - /* It is no longer running. We know that the child task - * was running okay when we started, so we must have lost - * the signal. In this case, we know that the task exit'ed, - * but we do not know its exit status. It would be better - * to reported ECHILD than bogus status. - */ + /* It is no longer running and there is no exit status (XSI, or + * another thread took it) + */ - ret = -ECHILD; - break; - } + ret = -ECHILD; + break; } #else /* CONFIG_SCHED_CHILD_STATUS */ @@ -201,6 +203,15 @@ int waittcb(FAR struct tcb_s *rtcb, pid_t pid, int options, if (info.si_signo == SIGCHLD && (pid == INVALID_PROCESS_ID || info.si_pid == pid)) { +#ifdef CONFIG_SCHED_CHILD_STATUS + if (!retains) + { + /* No exit status (XSI): check the children again */ + + continue; + } +#endif + /* Yes... return the status and PID (in the event it was -1) */ if (stat_loc != NULL)
