[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