1#include "sync/tests/test_internal.h"
2#include <mem/alloc_or_die.h>
3#include <sync/completion.h>
4#include <sync/condvar.h>
5#include <sync/semaphore.h>
6
7TEST_GROUP_DECLARE(condvar);
8TEST_GROUP_DECLARE(semaphore);
9TEST_GROUP_DECLARE(completion);
10
11struct timed_helper_args {
12 struct semaphore *sem;
13 struct completion *comp;
14 time_ms_t delay_ms;
15};
16
17static void timed_sem_poster(void *arg) {
18 struct timed_helper_args *a = arg;
19 sleep_spin_ms(msec: a->delay_ms);
20 semaphore_post(s: a->sem);
21}
22
23static void timed_comp_signaler(void *arg) {
24 struct timed_helper_args *a = arg;
25 sleep_spin_ms(msec: a->delay_ms);
26 complete(c: a->comp);
27}
28
29static void timed_comp_all_signaler(void *arg) {
30 struct timed_helper_args *a = arg;
31 sleep_spin_ms(msec: a->delay_ms);
32 complete_all(c: a->comp);
33}
34
35struct condvar_timeout_race {
36 struct condvar cv;
37 struct spinlock lock;
38 atomic_bool stop;
39 atomic_size_t completed;
40 atomic_bool wrong_reason;
41};
42
43static void condvar_timeout_race_worker(void *arg) {
44 struct condvar_timeout_race *race = arg;
45
46 for (size_t i = 0; i < 3000 && !atomic_load(&race->stop); i++) {
47 enum irql irql = spin_lock(&race->lock);
48 enum wake_reason reason =
49 condvar_wait_timeout(cv: &race->cv, lock: &race->lock, timeout_ms: 0, irql, out: &irql);
50 spin_unlock(&race->lock, irql);
51
52 if (reason != WAKE_REASON_TIMEOUT)
53 atomic_store(&race->wrong_reason, true);
54 atomic_fetch_add(&race->completed, 1);
55 }
56}
57
58TEST_DECLARE_UNIT(condvar, timeout_no_lost_wake) {
59 struct condvar_timeout_race race = {0};
60 condvar_init(cv: &race.cv, CONDVAR_INIT_NORMAL);
61 spinlock_init(&race.lock);
62
63 struct thread *t = thread_spawn_joinable(
64 name: "condvar_timeout_race", entry: condvar_timeout_race_worker, arg: &race);
65 TEST_ASSERT_NONNULL(t);
66
67 bool joined = thread_join_timeout(t, timeout_ms: 2500, NULL);
68 if (!joined) {
69 test_info("condvar timeout wake lost after %zu completed waits",
70 atomic_load(&race.completed));
71 atomic_store(&race.stop, true);
72 enum irql irql = spin_lock(&race.lock);
73 condvar_signal(cv: &race.cv);
74 spin_unlock(&race.lock, irql);
75 thread_join(t);
76 }
77
78 TEST_ASSERT(joined);
79 TEST_ASSERT_EQ(3000, atomic_load(&race.completed));
80 TEST_ASSERT(!atomic_load(&race.wrong_reason));
81 return TEST_SUCCESS;
82}
83
84TEST_DECLARE_UNIT(semaphore, timedwait) {
85 struct semaphore s;
86 semaphore_init(s: &s, value: 1, false);
87
88 TEST_ASSERT(semaphore_timedwait(&s, 50));
89 TEST_ASSERT_EQ(s.count, 0);
90
91 time_ms_t t0 = time_get_ms();
92 TEST_ASSERT(!semaphore_timedwait(&s, 30));
93 time_ms_t elapsed = time_get_ms() - t0;
94 TEST_ASSERT_GE(elapsed, 25);
95 TEST_ASSERT_EQ(s.count, 0);
96
97 struct timed_helper_args a = {
98 .sem = &s,
99 .delay_ms = 20,
100 };
101 struct thread *t =
102 alloc_or_die(thread_create("sem_poster", timed_sem_poster, &a));
103 thread_enqueue(t);
104
105 TEST_ASSERT(semaphore_timedwait(&s, 200));
106 TEST_ASSERT_EQ(s.count, 0);
107
108 return TEST_SUCCESS;
109}
110
111TEST_DECLARE_UNIT(completion, timedwait) {
112 struct completion c;
113 completion_init(c: &c, false);
114
115 time_ms_t t0 = time_get_ms();
116 TEST_ASSERT(!completion_wait_timeout(&c, 30));
117 time_ms_t elapsed = time_get_ms() - t0;
118 TEST_ASSERT_GE(elapsed, 25);
119
120 struct timed_helper_args a = {
121 .comp = &c,
122 .delay_ms = 20,
123 };
124 struct thread *t =
125 alloc_or_die(thread_create("comp_signaler", timed_comp_signaler, &a));
126 thread_enqueue(t);
127
128 TEST_ASSERT(completion_wait_timeout(&c, 200));
129 TEST_ASSERT(!completion_done(&c));
130
131 struct thread *t2 =
132 alloc_or_die(thread_create("comp_all", timed_comp_all_signaler, &a));
133 thread_enqueue(t: t2);
134
135 TEST_ASSERT(completion_wait_timeout(&c, 200));
136 TEST_ASSERT(completion_done(&c));
137
138 return TEST_SUCCESS;
139}
140