| 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 | |
| 9 | struct 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 | |
| 24 | static 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 | |
| 39 | static 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 | |
| 65 | static 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 | |
| 77 | TEST_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 | |