1#include "sync/tests/test_internal.h"
2#include <mem/nofail.h>
3#include <sync/condvar.h>
4#include <sync/semaphore.h>
5
6TEST_GROUP_DEFINE(condvar);
7TEST_GROUP_DEFINE(semaphore);
8
9struct timed_helper_args {
10 struct semaphore *sem;
11 time_ms_t delay_ms;
12};
13
14static 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
20struct 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
28static 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
43TEST_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
89TEST_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