1/* @title: Shared test concurrency core */
2#pragma once
3#include <atomic.h>
4#include <compiler/core.h>
5#include <crypto/prng.h>
6#include <smp/percpu.h>
7#include <stdbool.h>
8#include <stddef.h>
9#include <stdint.h>
10#include <stringify.h>
11#include <sync/event.h>
12#include <time/time.h>
13#include <types/types.h>
14#include <watchdog.h>
15
16/* Concurrency stuff that tests really like to use, primarily for
17 * thread fleets and other things. Nightmare tests and regular
18 * tests can use these. Some features:
19 *
20 * 1. Cooperative stops, so deadlines can tell workers to
21 * quit instead of abandoning them
22 *
23 * 2. Quiesce/park so coordinators can bring workers to a stop
24 *
25 * 3. Per-CPU progress counters for stall detection
26 */
27struct thread;
28
29enum test_stop : uint8_t {
30 TEST_RUN = 0,
31 TEST_STOP_BUDGET, /* soft deadline, wind down when out of time */
32 TEST_STOP_FINDING, /* worker reported a failure */
33 TEST_STOP_FAIL, /* harness-side failure, e.g. spawn failed */
34 TEST_STOP_STALL, /* hard deadline */
35};
36
37STRINGIFY_DECLARE_ENUM(test_stop) {
38 switch (stringify_in) {
39 case TEST_RUN: return "run";
40 case TEST_STOP_BUDGET: return "budget";
41 case TEST_STOP_FINDING: return "finding";
42 case TEST_STOP_FAIL: return "fail";
43 case TEST_STOP_STALL: return "stall";
44 default: cc_unreachable();
45 }
46}
47
48enum test_on_stall : uint8_t {
49 TEST_ON_STALL_REPORT = 0,
50 TEST_ON_STALL_CRASH,
51};
52
53enum test_liveness_phase : uint8_t {
54 TEST_LIVE_OFF = 0,
55 TEST_LIVE_ARMED,
56 TEST_LIVE_PENDING,
57 TEST_LIVE_REPORTED,
58};
59
60struct test_rng {
61 uint64_t state;
62};
63
64struct test_progress_counter {
65 atomic_uint64_t count;
66};
67
68struct test_stall_evidence {
69 time_ms_t silent_ms;
70 uint64_t progress;
71};
72
73struct test_conc_worker {
74 size_t index;
75 const char *role;
76 struct test_rng rng;
77 void *arg;
78 atomic(struct thread *) th;
79 atomic_bool parked;
80};
81
82struct test_conc {
83 atomic_bool active;
84
85 atomic(enum test_stop) stop;
86 atomic_bool quiesce_requested;
87 atomic_size_t parked_count;
88
89 struct test_conc_worker *workers cc_counted_by(worker_count);
90 size_t worker_count;
91
92 /* An extra thread that should be alerted on
93 * stop but isn't a worker, NULL if unused */
94 atomic(struct thread *) aux;
95
96 /* For telling all the workers 'go!' */
97 struct event start;
98};
99
100struct test_liveness_state {
101 struct watchdog_callback callback;
102 atomic(enum test_liveness_phase) phase;
103
104 uint64_t last_progress;
105 time_ms_t last_change_ms;
106
107 time_ms_t threshold_ms;
108 bool was_quiesced;
109
110 struct test_stall_evidence pending;
111 enum test_on_stall policy;
112 cpu_id_t coordinator_cpu;
113 bool registered;
114
115 struct test_conc *conc;
116};
117
118uint64_t test_rng_seed_for(uint64_t base_seed, size_t index);
119
120void test_conc_init(struct test_conc *c, struct test_conc_worker *workers,
121 size_t worker_count);
122
123void test_conc_publish_stop(struct test_conc *c, enum test_stop reason);
124
125void test_conc_park(struct test_conc *c, struct test_conc_worker *w);
126void test_conc_unpark_self(struct test_conc *c, struct test_conc_worker *w);
127
128/* This is a percpu heuristic, migrations are fine */
129uint64_t test_conc_progress_sum(void);
130
131bool test_liveness_start(struct test_liveness_state *state,
132 struct test_conc *conc, time_ms_t threshold_ms,
133 enum test_on_stall policy);
134
135void test_liveness_stop(struct test_liveness_state *state);
136
137/* Return stall evidence when pending, false if nothing to report */
138bool test_liveness_take(struct test_liveness_state *state,
139 struct test_stall_evidence *out);
140
141PERCPU_EXTERN(struct test_progress_counter, test_progress);
142static inline void test_conc_progress_tick(void) {
143 atomic_inc_rlx(&PERCPU_PTR(TOPC_NONE, test_progress)->count);
144}
145
146static inline uint64_t test_rng_next(struct test_rng *rng) {
147 return prng_splitmix64_next(state: &rng->state);
148}
149
150static inline bool test_conc_must_stop(const struct test_conc *c) {
151 return atomic_load_acq(&c->stop) != TEST_RUN;
152}
153
154static inline enum test_stop test_conc_stop_reason(const struct test_conc *c) {
155 return atomic_load_acq(&c->stop);
156}
157
158static inline bool test_conc_must_park(const struct test_conc *c) {
159 return atomic_load_acq(&c->quiesce_requested);
160}
161
162#define TEST_PROGRESS() test_conc_progress_tick()
163