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


The following commit(s) were added to refs/heads/master by this push:
     new 7797b122443 libc/atexit: honor registrations made during exit 
processing
7797b122443 is described below

commit 7797b1224433cd2324c1745fce91e77d1ad3696a
Author: Junbo Zheng <[email protected]>
AuthorDate: Sun Sep 13 18:07:43 2026 +0800

    libc/atexit: honor registrations made during exit processing
    
    atexit_call_exitfuncs() cached its loop bound on entry
    (for (idx = aehead->nfuncs - 1; idx >= 0; idx--)), while
    atexit_register() appends new entries at funcs[nfuncs] and bumps nfuncs.
    Any function registered by an exit handler via atexit() / on_exit() /
    __cxa_atexit() lands above the cached bound and is never invoked, even
    though the registration returns OK.
    
    This contradicts the exit(3) documentation that NuttX mirrors verbatim
    in its own exit() docstring (libs/libc/stdlib/lib_exit.c):
    
      It is possible for one of these functions to use atexit(3) or
      on_exit(3) to register an additional function to be executed
      during exit processing; the new registration is added to the
      front of the list of functions that remain to be called.
    
    The same restructure closes a second defect: atexit_call_exitfuncs()
    read and cleared the task-group-shared ta_exit list without holding
    ta_lock, while atexit_register() takes it ("The following must be
    atomic").  Entries are now claimed under the lock and the handler is
    invoked with the lock released, so a handler re-entering
    atexit_register() cannot deadlock (also safe with the non-recursive
    nxmutex used here).
    
    Evidence: exit(3) man page, DESCRIPTION -
    https://man7.org/linux/man-pages/man3/exit.3.html
    NuttX mirrors this passage verbatim in its own exit() docstring --
    
https://github.com/apache/nuttx/blob/5a209a853ec0dac623a2d5dfa81dea546b22820d/libs/libc/stdlib/lib_exit.c#L65-L70
    
    Before:
    ```
    A handler that registers another function during exit processing
    gets a success return from atexit(), but the new function is never
    invoked - it lands above the loop bound cached on entry.
    ```
    
    After:
    ```
    A registration made during exit processing runs before the older
    remaining handlers (order A -> B -> C below), matching the exit(3)
    guarantee, and the list is consumed under ta_lock.
    ```
    
    Testing:
    
    Simulated (sim:nsh, CONFIG_LIBC_MAX_EXITFUNS=8).
    
    Build and run:
    ```
    cmake -B build -DBOARD_CONFIG=sim:nsh -GNinja
    cmake -S . -B build   # after setting CONFIG_LIBC_MAX_EXITFUNS=8
                           # in build/.config (sim:nsh default is 1)
    cmake --build build -j$(nproc)
    (echo hello; echo poweroff) | ./build/nuttx
    ```
    "hello" runs the test at the NSH prompt; poweroff terminates the sim.
    
    The test was carried by apps/examples/hello/hello_main.c (scratch only,
    not part of this commit); its diff:
    
    ```
    --- a/examples/hello/hello_main.c
    +++ b/examples/hello/hello_main.c
    @@ -24,6 +24,7 @@
    
     #include <nuttx/config.h>
     #include <stdio.h>
    +#include <stdlib.h>
    
     
/****************************************************************************
      * Public Functions
    @@ -33,8 +34,29 @@
      * hello_main
      
****************************************************************************/
    
    +static void handler_b(void)
    +{
    +  printf("ATEXIT-TEST: handler B called (registered during exit)\n");
    +}
    +
    +static void handler_a(void)
    +{
    +  int ret;
    +
    +  printf("ATEXIT-TEST: handler A called\n");
    +  ret = atexit(handler_b);
    +  printf("ATEXIT-TEST: atexit(handler_b) inside A returned %d\n", ret);
    +}
    +
    +static void handler_c(void)
    +{
    +  printf("ATEXIT-TEST: handler C called\n");
    +}
    +
     int main(int argc, FAR char *argv[])
     {
       printf("Hello, World!!\n");
    +  atexit(handler_c);   /* older entry, must run LAST */
    +  atexit(handler_a);   /* registers handler_b during exit */
       return 0;
     }
    ```
    
    Before the fix:
    ```
    Hello, World!!
    ATEXIT-TEST: handler A called
    ATEXIT-TEST: atexit(handler_b) inside A returned 0
    ATEXIT-TEST: handler C called
    ```
    (handler B is never invoked although its registration returned 0)
    
    After the fix:
    ```
    Hello, World!!
    ATEXIT-TEST: handler A called
    ATEXIT-TEST: atexit(handler_b) inside A returned 0
    ATEXIT-TEST: handler B called (registered during exit)
    ATEXIT-TEST: handler C called
    ```
    
    Assisted-by: Claude Code (GLM-5.3) <[email protected]>
    Signed-off-by: Junbo Zheng <[email protected]>
---
 libs/libc/stdlib/lib_atexit.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/libs/libc/stdlib/lib_atexit.c b/libs/libc/stdlib/lib_atexit.c
index 5a3322c412c..cbc1e072b7f 100644
--- a/libs/libc/stdlib/lib_atexit.c
+++ b/libs/libc/stdlib/lib_atexit.c
@@ -110,6 +110,7 @@ int atexit_register(int type, CODE void (*func)(void), FAR 
void *arg,
 void atexit_call_exitfuncs(int status, bool quick)
 {
   FAR struct atexit_list_s *aehead;
+  FAR struct task_info_s   *info = task_get_info();
   CODE void               (*func)(void);
   FAR void                 *arg;
   int                       idx;
@@ -119,17 +120,32 @@ void atexit_call_exitfuncs(int status, bool quick)
 
   aehead = get_exitfuncs();
 
-  for (idx = aehead->nfuncs - 1; idx >= 0; idx--)
+  while (aehead->nfuncs > 0)
     {
-      /* Remove the function to prevent recursive call to it */
+      /* Claim the newest entry under the lock.  A handler may register
+       * further functions during exit processing; those land in the slot
+       * just freed here and are executed on the next iteration, i.e.
+       * before the older remaining ones, as documented in exit(3).
+       */
 
-      type = aehead->funcs[idx].type;
+      if (nxmutex_lock(&info->ta_lock) < 0)
+        {
+          break;
+        }
+
+      idx = aehead->nfuncs - 1;
 
+      type = aehead->funcs[idx].type;
       func = aehead->funcs[idx].func;
       arg  = aehead->funcs[idx].arg;
 
+      /* Remove the function to prevent recursive call to it */
+
       aehead->funcs[idx].func = NULL;
       aehead->funcs[idx].arg  = NULL;
+      aehead->nfuncs--;
+
+      nxmutex_unlock(&info->ta_lock);
 
       if (!func)
         {

Reply via email to