zdtm: retry pthread_create on EAGAIN in thread-bomb

The thread-bomb test frequently fails during setup with
pthread_create() returning EAGAIN (errno 11).  The test creates
1024 threads in a tight loop from main(), and each of those
threads immediately spawns another thread that joins its
predecessor, resulting in a burst of ~2048 simultaneous thread
creations with 64KB stacks.

This burst causes transient EAGAIN errors from clone() due to
kernel resource pressure (VMA allocator contention, temporary
memory fragmentation, etc.).  The failure is not related to hard
resource limits — ulimit, threads-max, max_map_count and cgroup
pids limits are all well above the required values.  The failure
occurs both inside and outside containers and is worse on hosts
with fewer resources.

Measured failure rates on a 16GB / 9-CPU host:
  Before fix: 65% failure rate (13/20 outside container)
  After fix:  ~2.5% failure rate (1/40), and that failure was
              a C/R issue, not a pthread_create EAGAIN

Fix this by adding a pthread_create_retry() wrapper that retries
pthread_create() up to 50 times with a 10ms delay when it returns
EAGAIN.  This gives the kernel time to reclaim resources between
attempts while keeping the total worst-case retry time under one
second per thread creation.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
This commit is contained in:
Adrian Reber 2026-03-17 12:20:03 +00:00 committed by Radostin Stoyanov
parent fc29bfef9d
commit cb8d1bec11

View file

@ -14,6 +14,29 @@ static pthread_attr_t attr;
/* Having in mind setup with 64 Kb large pages */
static const size_t stack_size = 64 * 1024;
#define EAGAIN_RETRIES 50
#define EAGAIN_DELAY_US 10000
/*
* Wrapper around pthread_create() that retries on EAGAIN. The test
* creates ~2048 threads in a burst and clone() can transiently fail
* with EAGAIN due to kernel resource pressure (VMA allocator
* contention, memory fragmentation, etc.) even when hard limits
* are not reached.
*/
static int pthread_create_retry(pthread_t *t, const pthread_attr_t *a,
void *(*fn)(void *), void *arg)
{
int err, retries = 0;
while (1) {
err = pthread_create(t, a, fn, arg);
if (err != EAGAIN || ++retries > EAGAIN_RETRIES)
return err;
usleep(EAGAIN_DELAY_US);
}
}
static void *thread_fn(void *arg)
{
pthread_t t, p, *self;
@ -37,7 +60,7 @@ static void *thread_fn(void *arg)
*self = pthread_self();
err = pthread_create(&t, &attr, thread_fn, self);
err = pthread_create_retry(&t, &attr, thread_fn, self);
if (err) {
pr_err("pthread_create(): %d\n", err);
free(self);
@ -73,7 +96,7 @@ int main(int argc, char **argv)
for (i = 0; i < max_nr; i++) {
pthread_t p;
err = pthread_create(&p, &attr, thread_fn, NULL);
err = pthread_create_retry(&p, &attr, thread_fn, NULL);
if (err) {
pr_err("pthread_create(): %d\n", err);
exit(1);