mirror of
https://github.com/torvalds/linux.git
synced 2026-10-15 20:30:02 +02:00
Several CI runs failed in the libarena parallel tests with -4 (-EINTR) [1],
which says nothing about what actually went wrong.
Two workers can fail like this:
worker 1: gives up, e.g. the rendezvous times out, sets test_abort and
returns its own error (-ETIMEDOUT)
worker 2: sees test_abort and returns -EINTR
-EINTR only means "someone else already gave up", so it carries no
information. Which of the two gets reported depends on the order
pthread_join() collects them, because
err = err ?: (long)thread_ret;
keeps the first non-zero value and drops the rest. When the -EINTR worker
comes first, the error describing the actual failure is lost.
Skip -EINTR entirely: a worker only returns it once another worker has
already reported the real error, so report and log only the real errors.
It is still unclear whether the timeouts come from CI load or from a
problem in the test itself. Report the error accurately first, so the next
failure can be diagnosed.
[1]:
https://github.com/kernel-patches/bpf/actions/runs/29867905253/job/88764463566
https://github.com/kernel-patches/bpf/actions/runs/29878191901/job/88794845824
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/bpf/20260723061347.398591-1-jiayuan.chen@linux.dev
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
274 lines
7.0 KiB
C
274 lines
7.0 KiB
C
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
|
|
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
|
#include <test_progs.h>
|
|
#include <unistd.h>
|
|
|
|
#include <libarena/common.h>
|
|
#include <libarena/asan.h>
|
|
#include <libarena/buddy.h>
|
|
#include <libarena/userspace.h>
|
|
|
|
#include "libarena/libarena.skel.h"
|
|
|
|
static void run_libarena_test(struct libarena *skel, struct bpf_program *prog,
|
|
const char *name)
|
|
{
|
|
int ret;
|
|
|
|
if (strstr(name, "test_buddy")) {
|
|
/* Buddy tests initialize the allocator directly. */
|
|
ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_destroy));
|
|
if (!ASSERT_OK(ret, "arena_buddy_destroy"))
|
|
return;
|
|
} else {
|
|
ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
|
|
if (!ASSERT_OK(ret, "arena_buddy_reset"))
|
|
return;
|
|
}
|
|
|
|
ret = libarena_run_prog(bpf_program__fd(prog));
|
|
|
|
ASSERT_OK(ret, name);
|
|
}
|
|
|
|
static void *run_libarena_parallel_prog(void *arg)
|
|
{
|
|
struct bpf_program *prog = arg;
|
|
|
|
return (void *)(long)libarena_run_prog(bpf_program__fd(prog));
|
|
}
|
|
|
|
/* Max suffix is ceil((lg 2^32) / (lg 10)) + sizeof("__") = 10 + 2 = 12. */
|
|
#define MAX_PARTEST_SUFFIX (12)
|
|
#define MAX_PARTEST_NAME (1024)
|
|
#define MAX_PARTEST_PREFIX (MAX_PARTEST_NAME - MAX_PARTEST_SUFFIX)
|
|
|
|
static int run_libarena_parallel_fini(struct libarena *skel, const char *name,
|
|
size_t prefixlen)
|
|
{
|
|
char tdname[MAX_PARTEST_NAME];
|
|
struct bpf_program *fini_prog;
|
|
int ret;
|
|
|
|
ret = snprintf(tdname, sizeof(tdname), "%.*s__fini", (int)prefixlen, name);
|
|
if (!ASSERT_LT(ret, sizeof(tdname), "partest fini name"))
|
|
return -ENAMETOOLONG;
|
|
|
|
fini_prog = bpf_object__find_program_by_name(skel->obj, tdname);
|
|
if (!ASSERT_TRUE(fini_prog, "partest fini prog"))
|
|
return -ENOENT;
|
|
|
|
ret = libarena_run_prog(bpf_program__fd(fini_prog));
|
|
ASSERT_OK(ret, tdname);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int run_libarena_parallel_test_workers(struct libarena *skel,
|
|
const char *name, size_t prefixlen)
|
|
{
|
|
pthread_t *threads = NULL, *tmp_threads;
|
|
char tdname[MAX_PARTEST_NAME];
|
|
struct bpf_program *tdprog;
|
|
uint32_t nthreads;
|
|
void *thread_ret;
|
|
int ret, err = 0;
|
|
int worker_err;
|
|
int i;
|
|
|
|
for (nthreads = 0; nthreads < UINT_MAX; nthreads++) {
|
|
ret = snprintf(tdname, sizeof(tdname), "%.*s__%u", (int)prefixlen,
|
|
name, nthreads);
|
|
if (!ASSERT_LT(ret, sizeof(tdname), "test worker name")) {
|
|
err = -ENAMETOOLONG;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We enumerate the worker threads for a given test with __0, __1,
|
|
* and so on. The suffixes always start from 0 and are contiguous,
|
|
* so if we don't find a program with the requested name we have
|
|
* discovered all available worker programs.
|
|
*/
|
|
tdprog = bpf_object__find_program_by_name(skel->obj, tdname);
|
|
if (!tdprog)
|
|
break;
|
|
|
|
/* Bump the alloc array to accommodate the new thread. */
|
|
tmp_threads = realloc(threads, (nthreads + 1) * sizeof(*threads));
|
|
if (!ASSERT_TRUE(tmp_threads, "realloc")) {
|
|
err = -ENOMEM;
|
|
break;
|
|
}
|
|
threads = tmp_threads;
|
|
|
|
ret = pthread_create(&threads[nthreads], NULL,
|
|
run_libarena_parallel_prog,
|
|
tdprog);
|
|
if (!ASSERT_OK(ret, "pthread_create")) {
|
|
err = ret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 0; i < nthreads; i++) {
|
|
ret = pthread_join(threads[i], &thread_ret);
|
|
if (!ASSERT_OK(ret, "pthread_join")) {
|
|
err = err ?: ret;
|
|
continue;
|
|
}
|
|
|
|
worker_err = (long)thread_ret;
|
|
|
|
/*
|
|
* A worker that bails out because another one already gave up
|
|
* reports -EINTR. It is collateral damage that carries no
|
|
* information, so skip it entirely: never let it become the
|
|
* reported error, and don't log it either.
|
|
*/
|
|
if (!worker_err || worker_err == -EINTR)
|
|
continue;
|
|
|
|
if (!err)
|
|
err = worker_err;
|
|
|
|
fprintf(stdout, "%.*s__%d returned %d\n", (int)prefixlen, name,
|
|
i, worker_err);
|
|
}
|
|
|
|
free(threads);
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool libarena_parallel_test_enabled(struct libarena *skel,
|
|
const char *prefix,
|
|
size_t prefixlen)
|
|
{
|
|
struct bpf_program *prog;
|
|
char progname[MAX_PARTEST_NAME];
|
|
int ret;
|
|
|
|
ret = snprintf(progname, sizeof(progname), "%.*s__enabled", (int)prefixlen,
|
|
prefix);
|
|
if (!ASSERT_LT(ret, sizeof(progname), "partest enabled name"))
|
|
return false;
|
|
|
|
prog = bpf_object__find_program_by_name(skel->obj, progname);
|
|
if (!prog)
|
|
return true;
|
|
|
|
ret = libarena_run_prog(bpf_program__fd(prog));
|
|
if (ret == -EOPNOTSUPP)
|
|
return false;
|
|
if (!ASSERT_OK(ret, progname))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static void run_libarena_parallel_test(struct libarena *skel, struct bpf_program *prog,
|
|
const char *name)
|
|
{
|
|
char testname[MAX_PARTEST_NAME];
|
|
size_t prefixlen;
|
|
const char *pos;
|
|
int ret;
|
|
|
|
/*
|
|
* We annotate the initialization prog with __init. If the current prog does
|
|
* not match, it is one of the parallel threads instead and is ignored.
|
|
*
|
|
* We assume the test writer knows what they are doing and do not add __init
|
|
* randomly in the middle of a test name.
|
|
*/
|
|
pos = strstr(name, "__init");
|
|
if (!pos)
|
|
return;
|
|
|
|
prefixlen = pos - name;
|
|
if (!ASSERT_LT(prefixlen, MAX_PARTEST_PREFIX, "partest prefix too long"))
|
|
return;
|
|
|
|
/* The name of the test without the __init suffix. Looks nicer in the test log. */
|
|
ret = snprintf(testname, sizeof(testname), "%.*s", (int)prefixlen, name);
|
|
if (!ASSERT_LT(ret, sizeof(testname), "partest test name"))
|
|
return;
|
|
|
|
if (!test__start_subtest(testname))
|
|
return;
|
|
|
|
if (!libarena_parallel_test_enabled(skel, testname, prefixlen)) {
|
|
test__skip();
|
|
return;
|
|
}
|
|
|
|
ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
|
|
if (!ASSERT_OK(ret, "arena_buddy_reset"))
|
|
return;
|
|
|
|
ret = libarena_run_prog(bpf_program__fd(prog));
|
|
if (!ASSERT_OK(ret, testname))
|
|
return;
|
|
|
|
ret = run_libarena_parallel_test_workers(skel, name, prefixlen);
|
|
|
|
ASSERT_OK(ret, testname);
|
|
|
|
run_libarena_parallel_fini(skel, name, prefixlen);
|
|
}
|
|
|
|
void serial_test_libarena(void)
|
|
{
|
|
struct arena_alloc_reserve_args args;
|
|
struct libarena *skel;
|
|
struct bpf_program *prog;
|
|
int ret;
|
|
|
|
skel = libarena__open_and_load();
|
|
if (!ASSERT_OK_PTR(skel, "open_and_load"))
|
|
return;
|
|
|
|
ret = libarena__attach(skel);
|
|
if (!ASSERT_OK(ret, "attach"))
|
|
goto out;
|
|
|
|
args.nr_pages = ARENA_RESERVE_PAGES_DFL;
|
|
|
|
ret = libarena_run_prog_args(bpf_program__fd(skel->progs.arena_alloc_reserve),
|
|
&args, sizeof(args));
|
|
if (!ASSERT_OK(ret, "arena_alloc_reserve"))
|
|
goto out;
|
|
|
|
bpf_object__for_each_program(prog, skel->obj) {
|
|
const char *name = bpf_program__name(prog);
|
|
|
|
/*
|
|
* Handle parallel test progs separately. For those
|
|
* progs it's not a matter of test/skip, because each
|
|
* parallel test prog includes an initialization prog
|
|
* and a set of progs to be run in parallel. For the
|
|
* latter we do not record them as skipped or run,
|
|
* because we run them all at once when we come across
|
|
* the initialization prog. For more details on how we
|
|
* discover the progs see the comment on
|
|
* run_libarena_parallel_test.
|
|
*/
|
|
if (libarena_is_parallel_test_prog(name)) {
|
|
run_libarena_parallel_test(skel, prog, name);
|
|
continue;
|
|
}
|
|
|
|
if (!libarena_is_test_prog(name))
|
|
continue;
|
|
|
|
if (!test__start_subtest(name))
|
|
continue;
|
|
|
|
run_libarena_test(skel, prog, name);
|
|
}
|
|
|
|
out:
|
|
libarena__destroy(skel);
|
|
}
|