mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-17 16:47:50 +00:00
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>
112 lines
2.1 KiB
C
112 lines
2.1 KiB
C
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <linux/unistd.h>
|
|
#include <syscall.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "zdtmtst.h"
|
|
|
|
#define exit_group(code) syscall(__NR_exit_group, code)
|
|
|
|
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;
|
|
int err;
|
|
|
|
if (arg) {
|
|
p = *(pthread_t *)arg;
|
|
err = pthread_join(p, NULL);
|
|
free(arg);
|
|
if (err) {
|
|
pr_err("pthread_join(): %d\n", err);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
self = malloc(sizeof(*self));
|
|
if (!self) {
|
|
pr_perror("malloc()");
|
|
return NULL;
|
|
}
|
|
|
|
*self = pthread_self();
|
|
|
|
err = pthread_create_retry(&t, &attr, thread_fn, self);
|
|
if (err) {
|
|
pr_err("pthread_create(): %d\n", err);
|
|
free(self);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int max_nr = 1024, i;
|
|
char *val;
|
|
int err;
|
|
|
|
test_init(argc, argv);
|
|
|
|
err = pthread_attr_init(&attr);
|
|
if (err) {
|
|
pr_err("pthread_attr_init(): %d\n", err);
|
|
exit(1);
|
|
}
|
|
|
|
err = pthread_attr_setstacksize(&attr, stack_size);
|
|
if (err) {
|
|
pr_err("pthread_attr_setstacksize(): %d\n", err);
|
|
exit(1);
|
|
}
|
|
|
|
val = getenv("ZDTM_THREAD_BOMB");
|
|
if (val)
|
|
max_nr = atoi(val);
|
|
|
|
test_msg("%d\n", max_nr);
|
|
|
|
for (i = 0; i < max_nr; i++) {
|
|
pthread_t p;
|
|
err = pthread_create_retry(&p, &attr, thread_fn, NULL);
|
|
if (err) {
|
|
pr_err("pthread_create(): %d\n", err);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
test_daemon();
|
|
test_waitsig();
|
|
|
|
pass();
|
|
|
|
return 0;
|
|
}
|