| 1 | #include "sync/tests/test_internal.h" |
| 2 | #include <mem/nofail.h> |
| 3 | #include <sync/condvar.h> |
| 4 | #include <sync/semaphore.h> |
| 5 | |
| 6 | TEST_GROUP_DEFINE(condvar); |
| 7 | TEST_GROUP_DEFINE(semaphore); |
| 8 | |
| 9 | struct timed_helper_args { |
| 10 | struct semaphore *sem; |
| 11 | time_ms_t delay_ms; |
| 12 | }; |
| 13 | |
| 14 | static void timed_sem_poster(void *arg) { |
| 15 | struct timed_helper_args *a = arg; |
| 16 | sleep_spin_ms(msec: a->delay_ms); |
| 17 | semaphore_post(s: a->sem); |
| 18 | } |
| 19 | |
| 20 | struct condvar_timeout_race { |
| 21 | struct condvar cv; |
| 22 | struct spinlock lock; |
| 23 | atomic_bool stop; |
| 24 | atomic_size_t completed; |
| 25 | atomic_bool wrong_reason; |
| 26 | }; |
| 27 | |
| 28 | static void condvar_timeout_race_worker(void *arg) { |
| 29 | struct condvar_timeout_race *race = arg; |
| 30 | |
| 31 | for (size_t i = 0; i < 3000 && !atomic_load(&race->stop); i++) { |
| 32 | enum irql irql = spin_lock(&race->lock); |
| 33 | enum wake_reason reason = |
| 34 | condvar_wait_timeout(cv: &race->cv, lock: &race->lock, timeout_ms: 0, irql, out: &irql); |
| 35 | spin_unlock(&race->lock, irql); |
| 36 | |
| 37 | if (reason != WAKE_REASON_TIMEOUT) |
| 38 | atomic_store(&race->wrong_reason, true); |
| 39 | atomic_inc(&race->completed); |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | TEST_DEFINE_UNIT(condvar, timeout_no_lost_wake) { |
| 44 | static const time_ms_t progress_timeout_ms = 1000; |
| 45 | static const size_t stalled_progress_limit = 2; |
| 46 | |
| 47 | struct condvar_timeout_race race = {0}; |
| 48 | condvar_init(cv: &race.cv); |
| 49 | spinlock_init(&race.lock); |
| 50 | |
| 51 | struct thread *t = |
| 52 | thread_spawn("condvar_timeout_race" , condvar_timeout_race_worker, |
| 53 | .arg = &race, .joinable = true); |
| 54 | TEST_ASSERT_NONNULL(t); |
| 55 | |
| 56 | bool joined = false; |
| 57 | size_t last_completed = 0; |
| 58 | size_t stalled_progress = 0; |
| 59 | |
| 60 | while (!(joined = thread_join_timeout(t, timeout_ms: progress_timeout_ms, NULL))) { |
| 61 | size_t completed = atomic_load(&race.completed); |
| 62 | if (completed == last_completed) { |
| 63 | stalled_progress++; |
| 64 | } else { |
| 65 | last_completed = completed; |
| 66 | stalled_progress = 0; |
| 67 | } |
| 68 | |
| 69 | if (stalled_progress == stalled_progress_limit) |
| 70 | break; |
| 71 | } |
| 72 | |
| 73 | if (!joined) { |
| 74 | test_info("condvar timeout worker stalled after %zu completed waits" , |
| 75 | last_completed); |
| 76 | atomic_store(&race.stop, true); |
| 77 | enum irql irql = spin_lock(&race.lock); |
| 78 | condvar_signal(cv: &race.cv); |
| 79 | spin_unlock(&race.lock, irql); |
| 80 | thread_join(t); |
| 81 | } |
| 82 | |
| 83 | TEST_ASSERT(joined); |
| 84 | TEST_ASSERT_EQ(3000, atomic_load(&race.completed)); |
| 85 | TEST_ASSERT(!atomic_load(&race.wrong_reason)); |
| 86 | return TEST_SUCCESS; |
| 87 | } |
| 88 | |
| 89 | TEST_DEFINE_UNIT(semaphore, timedwait_standard) { |
| 90 | struct semaphore s; |
| 91 | semaphore_init(s: &s, value: 1, false); |
| 92 | |
| 93 | TEST_ASSERT(semaphore_timedwait(&s, 50)); |
| 94 | TEST_ASSERT_EQ(s.count, 0); |
| 95 | |
| 96 | time_ms_t t0 = time_get_ms(); |
| 97 | TEST_ASSERT(!semaphore_timedwait(&s, 30)); |
| 98 | time_ms_t elapsed = time_get_ms() - t0; |
| 99 | TEST_ASSERT_GE(elapsed, 25); |
| 100 | TEST_ASSERT_EQ(s.count, 0); |
| 101 | |
| 102 | struct timed_helper_args a = { |
| 103 | .sem = &s, |
| 104 | .delay_ms = 20, |
| 105 | }; |
| 106 | struct thread *t = |
| 107 | nofail(thread_create("sem_poster" , timed_sem_poster, .arg = &a)); |
| 108 | thread_enqueue(t); |
| 109 | |
| 110 | TEST_ASSERT(semaphore_timedwait(&s, 200)); |
| 111 | TEST_ASSERT_EQ(s.count, 0); |
| 112 | |
| 113 | return TEST_SUCCESS; |
| 114 | } |
| 115 | |