Skip to content

Test worker fleet

include/test/fleet.h View source View on GitHub

Worker thread fleets

The idea here: we have a pointer to one of these in struct test_context.

When a test needs a fleet, it simply test_fleet_init()’s the ctx fleet, and the runner will handle cleanup.

NOTE

Tests must not free their fleets, and

TODO

Add mechanisms to track the running context (test, harness, etc.) so in the future, we can detect and panic if a test tries to free its fleet

struct test_fleet_worker_opts (5 fields) source

Options for spawning individual workers in a fleet

§ bool pinned
§ void *arg
§ cpu_id_t core_id

CPU_ID_MAX = unpinned

§ enum thread_prio_class prio_class
§ enum thread_flags flags
struct test_fleet_opts (4 fields) source
§ time_ms_t soft_ms

0 falls back to defaults

§ time_ms_t drain_ms
§ bool run_until_stop

Workers run until they get stopped

§ bool pin

Pin on whatever CPU the scheduler decides it wants to put the thread on, which can help increase contention and stop the balancer from distributing threads

struct test_fleet (19 fields) source
§ struct test_context *ctx
§ struct test_conc conc
§ size_t count
§ struct test_fleet_opts opts
§ struct test_fleet_alloc *allocs

This is a wrapper to prevent tests from manually managing their memory. It’s the head of a SLIST of nodes, and at test exit, we iterate over it and free everything

TODO

We really need to make such abstractions a global primitive to embed allocators instead of making them ad-hoc.

§ size_t subject_count

workers[0, subject_count) are subjects, and the rest are perturbers

§ bool has_perturbers
§ struct test_perturb_env perturb_env
§ const char *fail_file
§ uint32_t fail_line
§ atomic_size_t started
§ atomic_size_t finished
§ struct test_liveness_state liveness
§ struct timer hard_timer
§ bool hard_armed
§ bool joined
§ bool spawn_failed
NOTE

this seems odd to even handle, but down the line, say, when kernel tests are runnable from userspace, we can’t just panic if an allocation fails (unfortunate)

typedef bool (*test_worker_fn)(
    struct test_fleet * f,
    struct test_conc_worker * self
)
source

Worker threads return false to say “I tripped some assertion”, and the outer loop captures that

struct test_fleet * test_fleet_init_full(
    struct test_context *ctx,
    const struct test_fleet_opts opts
)
source
void * test_fleet_alloc(struct test_fleet *f, size_t size)
source
struct test_conc_worker * test_fleet_spawn_full(
    struct test_fleet *f,
    const char *role,
    test_worker_fn body,
    const struct test_fleet_worker_opts opts
)
source

Spawn with custom worker options (affinity, priority class, flags)

struct thread * test_fleet_worker_thread(
    struct test_fleet *f,
    const char *role
)
source

Look up a worker thread by its role name

size_t test_fleet_spawn_per_core(
    struct test_fleet *f,
    const char *role,
    test_worker_fn body,
    void *arg,
    size_t per_core
)
source

Spawn N many per CPU

size_t test_fleet_spawn_perturbers(
    struct test_fleet *f,
    const char *const *names,
    int64_t victim_cpu
)
source

Background noise against every worker, one perturber per name

void test_fleet_start_all(struct test_fleet *f)
source

Open the gates for the workers

struct test_verdict test_fleet_join(struct test_fleet *f)
source
bool test_fleet_must_stop(const struct test_fleet *f)
source
bool test_fleet_must_park(const struct test_fleet *f)
source
void test_fleet_park(struct test_fleet *f, struct test_conc_worker *w)
source
void test_fleet_stop(struct test_fleet *f, enum test_stop reason)
source
bool test_run_on_core(cpu_id_t core_id, void (*fn)(void *), void *arg)
source

Runs a singular function on a given CPU from another thread’s context, and then waits to join

bool test_fleet_teardown(struct test_context *ctx)
source
NOTE

runner context only, NOT in the test

#define TEST_FLEET_MAX_WORKERS 128
source
TODO

Bump/scale around with CPU count and whatnot

#define TEST_FLEET_SOFT_MS 30000
source
#define TEST_FLEET_DRAIN_MS 10000
source

Give some leeway for the workers to calm down

#define test_fleet_opts_with_defaults(...)
source
Expansion
#define test_fleet_opts_with_defaults(...) \
    cc_wno_override_init_expr( \
        struct test_fleet_opts, \
        ((struct test_fleet_opts) {.soft_ms = TEST_FLEET_SOFT_MS, \
                                   .drain_ms = TEST_FLEET_DRAIN_MS, \
                                   ##__VA_ARGS__}))
#define test_fleet_init(ctx, ...)
source
Expansion
#define test_fleet_init(ctx, ...) \
    test_fleet_init_full((ctx), test_fleet_opts_with_defaults(__VA_ARGS__))
#define test_fleet_worker_opts_with_defaults(...)
source
Expansion
#define test_fleet_worker_opts_with_defaults(...) \
    cc_wno_override_init_expr(struct test_fleet_worker_opts, \
                              ((struct test_fleet_worker_opts) { \
                                  .core_id = CPU_ID_MAX, \
                                  .prio_class = THREAD_PRIO_CLASS_TIMESHARE, \
                                  .pinned = false, \
                                  ##__VA_ARGS__}))
#define test_fleet_spawn(f, role, body, ...)
source
Expansion
#define test_fleet_spawn(f, role, body, ...) \
    test_fleet_spawn_full((f), (role), (body), \
                          test_fleet_worker_opts_with_defaults(__VA_ARGS__))