[PATCH] Do not translate ENOMEM to EAGAIN in pthread_create

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Jul 8 12:00:51 GMT 2026



On 08/07/26 08:04, Florian Weimer wrote:
> Applications may want to retry on EAGAIN errors, but not on ENOMEM
> errors.  If we translate ENOMEM errors, this is not possible.

I think we should create a bug report for this issue.  Just a suggestion
below, the rest of the patch looks OK.

> 
> ---
>  NEWS                                |   6 ++
>  htl/pt-create.c                     |   2 -
>  manual/threads.texi                 |  22 +++++-
>  nptl/pthread_create.c               |  30 +++-----
>  sysdeps/pthread/Makefile            |   1 +
>  sysdeps/pthread/tst-basic7.c        |  15 ++--
>  sysdeps/pthread/tst-thread-basic8.c | 132 ++++++++++++++++++++++++++++++++++++
>  7 files changed, 174 insertions(+), 34 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index f9d90c5194..02eed38112 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -57,6 +57,12 @@ Deprecated and removed features, and other changes affecting compatibility:
>    remaining behavior was to suppress the link-time warnings on the NSS
>    interface functions in libc.a, which are now emitted unconditionally.
>  
> +* The pthread_create function no longer translates ENOMEM errors to
> +  EAGAIN errors.  On some systems, true EAGAIN errors can be caused by
> +  temporary conditions and a repeated pthread_create call may succeed.
> +  In contrast, ENOMEM errors are likely to persist until some memory is
> +  deallocated.
> +
>  Changes to build and runtime requirements:
>  
>    [Add changes to build and runtime requirements here]
> diff --git a/htl/pt-create.c b/htl/pt-create.c
> index a66b6ebdcf..6c9d5912a4 100644
> --- a/htl/pt-create.c
> +++ b/htl/pt-create.c
> @@ -104,8 +104,6 @@ __libc_pthread_create (pthread_t * thread, const pthread_attr_t * attr,
>    err = __pthread_create_internal (&pthread, attr, start_routine, arg);
>    if (!err)
>      *thread = pthread->thread;
> -  else if (err == ENOMEM)
> -    err = EAGAIN;
>  
>    return err;
>  }
> diff --git a/manual/threads.texi b/manual/threads.texi
> index 0331599bc0..2f43f7f917 100644
> --- a/manual/threads.texi
> +++ b/manual/threads.texi
> @@ -593,7 +593,27 @@ This function creates a new thread with attributes @var{attr}.  This
>  thread will call @var{start_routine} and pass it @var{arg}.  If
>  @var{start_routine} returns, the thread will exit and the return value
>  will become the thread's exit value.  The new thread's ID is stored in
> -@var{newthread}.  Returns 0 on success.
> +@var{newthread}.  Returns 0 on success, and an error code on failure.
> +Among the failure codes are:
> +
> +@table @code
> +@item EAGAIN
> +The thread could not be created because the maximum thread count for the
> +process, user, or system has been reached.  On Linux, this error can be
> +spurious, and calling @code{pthread_create} again may succeed although
> +no threads have exited since the previous call.
> +
> +@item ENOMEM
> +Memory for the new thread could not be allocated.  This can refer to
> +memory for the stack, thread-local storage, or administrative data.
> +
> +POSIX suggests reporting these error conditions as @code{EAGAIN}, but
> +@theglibc{} uses @code{ENOMEM} instead.  Out-of-memory conditions
> +typically are more persistent than @code{EAGAIN} errors, which can be
> +spurious.  Separate error codes allow applications to handle these
> +conditions differently.
> +@end table
> +
>  @manpagefunctionstub{pthread_create, 3}
>  @end deftypefun
>  
> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
> index fcb06c68c7..3e3c3b1016 100644
> --- a/nptl/pthread_create.c
> +++ b/nptl/pthread_create.c
> @@ -214,7 +214,7 @@ late_init (void)
>     action before calling the user entry point.  The goal of all of this
>     is to ensure that the required initial thread attributes are applied
>     (by the creating thread) before the new thread runs user code.  Note
> -   that the the functions pthread_getschedparam, pthread_setschedparam,
> +   that the functions pthread_getschedparam, pthread_setschedparam,
>     pthread_setschedprio, __pthread_tpp_change_priority, and
>     __pthread_current_priority reuse the same lock, PD->lock, for a
>     similar purpose e.g. synchronizing the setting of similar thread
> @@ -224,10 +224,10 @@ late_init (void)
>     it saves space.
>  
>     The return value is zero for success or an errno code for failure.
> -   If the return value is ENOMEM, that will be translated to EAGAIN,
> -   so create_thread need not do that.  On failure, *THREAD_RAN should
> -   be set to true iff the thread actually started up but before calling
> -   the user code (*PD->start_routine).  */
> +   On failure, *THREAD_RAN should be set to true iff the thread has
> +   started and the failure occurred before running user code
> +   (*PD->start_routine); therefore, the thread needs to be joined
> +   before reporting the error to the application.  */
>  
>  static int _Noreturn start_thread (void *arg);
>  
> @@ -235,7 +235,7 @@ static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
>  			  bool *stopped_start, void *stackaddr,
>  			  size_t stacksize, bool *thread_ran)
>  {
> -  /* Determine whether the newly created threads has to be started
> +  /* Determine whether the newly created thread has to be started
>       stopped since we have to set the scheduling parameters or set the
>       affinity.  */
>    bool need_setaffinity = (attr != NULL && attr->extension != NULL
> @@ -677,18 +677,12 @@ __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
>      }
>  
>    struct pthread *pd = NULL;
> -  int err = allocate_stack (iattr, &pd, &stackaddr, &stacksize);
> -  int retval = 0;
> +  int retval = allocate_stack (iattr, &pd, &stackaddr, &stacksize);
>  
> -  if (__glibc_unlikely (err != 0))
> +  if (__glibc_unlikely (retval != 0))
>      /* Something went wrong.  Maybe a parameter of the attributes is
> -       invalid or we could not allocate memory.  Note we have to
> -       translate error codes.  */
> -    {
> -      retval = err == ENOMEM ? EAGAIN : err;
> -      goto out;
> -    }
> -
> +       invalid or we could not allocate memory.  */
> +    goto out;
>  
>    /* Initialize the TCB.  All initializations with zero should be
>       performed in 'get_cached_stack'.  This way we avoid doing this if
> @@ -893,10 +887,6 @@ __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
>  
>        /* Free the resources.  */
>        __nptl_deallocate_stack (pd);
> -
> -      /* We have to translate error codes.  */
> -      if (retval == ENOMEM)
> -	retval = EAGAIN;
>      }
>    else
>      {
> diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
> index d0f3cd59ac..3e8fd1de32 100644
> --- a/sysdeps/pthread/Makefile
> +++ b/sysdeps/pthread/Makefile
> @@ -275,6 +275,7 @@ tests += \
>    tst-stdio2 \
>    tst-thrd-detach \
>    tst-thrd-sleep \
> +  tst-thread-basic8 \
>    tst-tsd1 \
>    tst-tsd2 \
>    tst-tsd5 \
> diff --git a/sysdeps/pthread/tst-basic7.c b/sysdeps/pthread/tst-basic7.c
> index 26a599c178..0db7ccb352 100644
> --- a/sysdeps/pthread/tst-basic7.c
> +++ b/sysdeps/pthread/tst-basic7.c
> @@ -7,6 +7,7 @@
>  #include <unistd.h>
>  #include <sys/mman.h>
>  #include <sys/resource.h>
> +#include <support/check.h>
>  
>  static void use_stack (size_t needed);
>  
> @@ -51,7 +52,6 @@ child (void *arg)
>  static int
>  do_test (void)
>  {
> -  int err;
>    pthread_t tid;
>  
>    /* Allocate the memory needed for the stack.  */
> @@ -63,17 +63,10 @@ do_test (void)
>  
>    use_up_memory ();
>  
> -  err = pthread_create (&tid, NULL, child, NULL);
> -  if (err != 0)
> -    {
> -      printf ("pthread_create returns %d: %s\n", err,
> -	      err == EAGAIN ? "OK" : "FAIL");
> -      return err != EAGAIN;
> -    }
> +  errno = pthread_create (&tid, NULL, child, NULL);
> +  TEST_COMPARE (errno, ENOMEM);
>  
> -  /* We did not fail to allocate memory despite the preparation.  Oh well.  */
>    return 0;
>  }
>  
> -#define TEST_FUNCTION do_test ()
> -#include "../test-skeleton.c"
> +#include <support/test-driver.c>
> diff --git a/sysdeps/pthread/tst-thread-basic8.c b/sysdeps/pthread/tst-thread-basic8.c
> new file mode 100644
> index 0000000000..18fe298a20
> --- /dev/null
> +++ b/sysdeps/pthread/tst-thread-basic8.c
> @@ -0,0 +1,132 @@
> +/* Check that repeated pthread_create eventually fails with EAGAIN.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <errno.h>
> +#include <pthread.h>
> +#include <stdbool.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xstdio.h>
> +#include <support/xthread.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <sys/resource.h>
> +
> +/* Used to block just-launched threads in a barrier, to avoid running
> +   too many threads in parallel.  */
> +static pthread_barrier_t barrier;
> +
> +/* List of threads that have been created.  */
> +struct thr
> +{
> +  pthread_t id;
> +  struct thr *next;
> +};
> +
> +static void *
> +thread_routine (void *ignored)
> +{
> +  xpthread_barrier_wait (&barrier);
> +  pause ();
> +  return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{

I think it would be better add support_need_proc and remove the 500 fallback
below.  The /proc/loadavg, as other long-standing procfs file, would hardily
change without breaking some other code.


> +  /* Reduce the number of supported threads.  */
> +  {
> +    /* Compute a target thread limit based on the number of running
> +       threads on the system.  500 is just a random guess if the value
> +       cannot be read.  */
> +    int target = 500;
> +    {
> +      FILE *fp = fopen ("/proc/loadavg", "r");
> +      if (fp != NULL)
> +        {
> +          float ignored;
> +          int count;
> +          if (fscanf (fp, "%f %f %f %f/%d",
> +                      &ignored, &ignored, &ignored, &ignored, &count)
> +              == 5)
> +            target = count + 100;
> +          xfclose (fp);
> +        }
> +    }
> +
> +    struct rlimit rl;
> +    if (getrlimit (RLIMIT_NPROC, &rl) != 0)
> +      FAIL_EXIT1 ("getrlimit (RLIMIT_NPROC): %m");
> +    if (rl.rlim_cur == RLIM_INFINITY || rl.rlim_cur > target)
> +      {
> +        printf ("info: setting thread limit to %d\n", target);
> +        rl.rlim_cur = target;
> +        if (setrlimit (RLIMIT_NPROC, &rl) != 0)
> +          FAIL_EXIT1 ("setrlimit (RLIMIT_NPROC): %m");
> +      }
> +  }
> +
> +  pthread_attr_t attr;
> +  xpthread_attr_init (&attr);
> +  support_set_small_thread_stack_size (&attr, false);
> +
> +  xpthread_barrier_init (&barrier, NULL, 2);
> +
> +  struct thr *list = NULL;
> +
> +  /* Create threads until creation fails.  This typically fails sooner
> +     than the target above because the user already has some number of
> +     threads running.  */
> +  while (true)
> +    {
> +      pthread_t id;
> +      errno = pthread_create (&id, &attr, thread_routine, NULL);
> +      if (errno == EAGAIN)
> +        break;
> +      /* Only EAGAIN errors are expected.  With a small stack size,
> +         expect to run out of threads before hitting the memory
> +         allocation failure.  */
> +      if (errno > 0)
> +        FAIL_EXIT1 ("pthread_create: %m");
> +
> +      struct thr *e = xmalloc (sizeof (*e));
> +      e->id = id;
> +      e->next = list;
> +      list = e;
> +
> +      xpthread_barrier_wait (&barrier);
> +    }
> +
> +  /* Terminate the threads.  */
> +  while (list != NULL)
> +    {
> +      struct thr *e = list;
> +      list = list->next;
> +
> +      xpthread_cancel (e->id);
> +      TEST_VERIFY (xpthread_join (e->id) == PTHREAD_CANCELED);
> +      free (e);
> +    }
> +
> +  xpthread_barrier_destroy (&barrier);
> +  xpthread_attr_destroy (&attr);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> 
> base-commit: 0f61d77aef1bcd5977211e87e0496768f5650c0e
> 



More information about the Libc-alpha mailing list