Do not translate ENOMEM to EAGAIN in pthread_create
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
| linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
Commit Message
Applications may want to retry on EAGAIN errors, but not on ENOMEM
errors. If we translate ENOMEM errors, this is not possible.
---
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(-)
base-commit: 0f61d77aef1bcd5977211e87e0496768f5650c0e
Comments
On Jul 08 2026, Florian Weimer wrote:
> 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.
Perhaps s/although/even if/.
> +@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},
I don't think it is a suggestion, but a shall.
* Andreas Schwab:
> On Jul 08 2026, Florian Weimer wrote:
>
>> 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.
>
> Perhaps s/although/even if/.
In general, I lean heavily towards “even if”, so I try to compensate.
>> +@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},
>
> I don't think it is a suggestion, but a shall.
What does this mean for glibc? I think the proposed change is genuinely
useful to some applications like OpenJDK.
Thanks,
Florian
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
>
@@ -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]
@@ -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;
}
@@ -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
@@ -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
{
@@ -275,6 +275,7 @@ tests += \
tst-stdio2 \
tst-thrd-detach \
tst-thrd-sleep \
+ tst-thread-basic8 \
tst-tsd1 \
tst-tsd2 \
tst-tsd5 \
@@ -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>
new file mode 100644
@@ -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)
+{
+ /* 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>