[PATCH] Do not translate ENOMEM to EAGAIN in pthread_create

enh enh@google.com
Tue Jul 21 18:35:31 GMT 2026


did anyone bring this up with the austin group already, or should i file a bug?

On Wed, Jul 8, 2026 at 8:00 AM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
>
>
>
> 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