1#include "sch/tests/test_internal.h"
2
3#define MIGRATE_KICK_MAX_MS 10
4
5#define MIGRATE_KICK_STRAND_MS 1000
6
7#define MIGRATE_KICK_SETTLE_MS 50
8
9struct migrate_kick {
10 cpu_id_t main_cpu;
11 atomic_bool go;
12
13 /* Chosen by the worker once it is running */
14 atomic_int64_t src;
15 atomic_int64_t dst;
16 atomic_bool dst_was_idle;
17
18 time_ms_t yielded_ms;
19 time_ms_t resumed_ms;
20 atomic_int64_t resumed_on;
21 atomic_bool done;
22};
23
24static int64_t migrate_kick_pick_dst(cpu_id_t self, cpu_id_t main_cpu) {
25 size_t n = global.core_count;
26
27 for (size_t step = 0; step < n; step++) {
28 size_t cpu = (self + n / 2 + step) % n;
29 if (cpu == self || cpu == main_cpu)
30 continue;
31
32 if (scheduler_core_idle(c: global.cores[cpu]))
33 return (int64_t) cpu;
34 }
35
36 return -1;
37}
38
39static void migrate_kick_worker(void *arg) {
40 struct migrate_kick *mk = arg;
41
42 while (!atomic_load_acq(&mk->go))
43 cpu_pause();
44
45 cpu_id_t self = smp_id(cond: TOPC_NONE);
46 int64_t dst = migrate_kick_pick_dst(self, main_cpu: mk->main_cpu);
47
48 atomic_store_rlx(&mk->src, (int64_t) self);
49 atomic_store_rlx(&mk->dst, dst);
50
51 if (dst >= 0) {
52 atomic_store_rlx(&mk->dst_was_idle,
53 scheduler_core_idle(global.cores[dst]));
54 thread_set_migration_target(t: thread_get_current(), new: dst);
55
56 mk->yielded_ms = time_get_ms();
57 scheduler_yield();
58 mk->resumed_ms = time_get_ms();
59 }
60
61 atomic_store_rlx(&mk->resumed_on, (int64_t) smp_id(TOPC_NONE));
62 atomic_store_release(&mk->done, true);
63}
64
65static bool migrate_kick_wait(struct migrate_kick *mk, time_ms_t budget_ms) {
66 time_ms_t start = time_get_ms();
67
68 while (!atomic_load_acq(&mk->done)) {
69 if (time_get_ms() - start >= budget_ms)
70 return false;
71 thread_sleep_for_ms(ms: 1);
72 }
73
74 return true;
75}
76
77TEST_DEFINE_INTEGRATION(sched, migrate_target_kicks_idle, .min_cores = 4) {
78 struct migrate_kick *mk = vnew(mk, ALLOC_ZERO);
79 TEST_ASSERT_NONNULL(mk);
80
81 mk->main_cpu = smp_id(cond: TOPC_NONE);
82 atomic_store_rlx(&mk->dst, -1);
83
84 cpu_id_t src = (mk->main_cpu + 1) % global.core_count;
85 struct thread *t = thread_spawn(("migrate_kick"), migrate_kick_worker,
86 .arg = mk, .on_cpu = src, .joinable = true);
87 TEST_ASSERT_NONNULL(t);
88
89 thread_sleep_for_ms(MIGRATE_KICK_SETTLE_MS);
90 atomic_store_release(&mk->go, true);
91
92 bool stranded = !migrate_kick_wait(mk, MIGRATE_KICK_STRAND_MS);
93 int64_t dst = atomic_load_rlx(&mk->dst);
94
95 if (stranded && dst >= 0)
96 scheduler_force_resched(sched: global.schedulers[dst]);
97
98 thread_join(t);
99
100 int64_t resumed_on = atomic_load_rlx(&mk->resumed_on);
101 time_ms_t latency = mk->resumed_ms - mk->yielded_ms;
102 bool dst_was_idle = atomic_load_rlx(&mk->dst_was_idle);
103 int64_t from = atomic_load_rlx(&mk->src);
104 vfree(mk);
105
106 if (dst < 0)
107 return TEST_SKIP(TEST_SKIP_NONE);
108
109 TEST_ASSERT_MSG(dst_was_idle, "destination cpu %ld was busy at go",
110 (long) dst);
111
112 TEST_ASSERT_MSG(!stranded,
113 "thread migrated cpu %ld -> idle cpu %ld stranded on its "
114 "runqueue for %u ms without a kick",
115 (long) from, (long) dst, MIGRATE_KICK_STRAND_MS);
116
117 TEST_ASSERT_MSG(latency <= MIGRATE_KICK_MAX_MS,
118 "thread migrated cpu %ld -> idle cpu %ld took %lu ms to "
119 "run, so the destination was never kicked",
120 (long) from, (long) dst, (unsigned long) latency);
121
122 TEST_ASSERT_EQ(resumed_on, dst);
123 return TEST_SUCCESS;
124}
125