[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