Re: [PATCH] Do not translate ENOMEM to EAGAIN in pthread_create
enh <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <CAJgzZoqmbQcr3OEYMK4NoGnJgF6QhBpQ9GOWSx5VrhCd0AgeUg@mail.gmail.com> |
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 <[email protected]> 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 > > >