1#include <string.h>
2#include <sync/event.h>
3
4#include "sync/tests/test_internal.h"
5
6TEST_GROUP_DEFINE(event);
7
8static size_t linked_waiters(struct event *e) {
9 size_t n = 0;
10 struct list_node *pos;
11
12 enum irql irql = spin_lock_high(&e->header.lock);
13 list_for_each(pos, &e->header.waiters)
14 n++;
15 spin_unlock(&e->header.lock, irql);
16
17 return n;
18}
19
20static void wait_for_linked(struct event *e, size_t n) {
21 while (linked_waiters(e) < n)
22 scheduler_yield();
23}
24
25struct event_waiter_args {
26 struct event *e;
27 atomic_uint *done;
28 struct thread_wait_result result;
29};
30
31static void event_waiter(void *arg) {
32 struct event_waiter_args *a = arg;
33 a->result = event_wait(e: a->e, type: THREAD_WAIT_UNINTERRUPTIBLE,
34 reason: THREAD_BLOCK_REASON_MANUAL);
35 atomic_inc(a->done);
36}
37
38TEST_DEFINE_UNIT(event, sync_latch_and_consume) {
39 struct event e;
40 event_init(e: &e, type: EVENT_SYNCHRONIZATION, false);
41
42 TEST_ASSERT(!event_set(&e));
43 TEST_ASSERT(event_set(&e)); /* Already latched */
44 TEST_ASSERT(event_read_state(&e));
45
46 struct thread_wait_result r =
47 event_wait(e: &e, type: THREAD_WAIT_UNINTERRUPTIBLE, reason: THREAD_BLOCK_REASON_MANUAL);
48 TEST_ASSERT_EQ(r.status, THREAD_WAIT_SATISFIED);
49 TEST_ASSERT(!event_read_state(&e));
50 TEST_ASSERT(list_head_empty(&e.header.waiters));
51 return TEST_SUCCESS;
52}
53
54TEST_DEFINE_UNIT(event, sync_hands_off_to_waiter) {
55 struct event e;
56 atomic_uint done = 0;
57 event_init(e: &e, type: EVENT_SYNCHRONIZATION, false);
58
59 struct event_waiter_args a = {.e = &e, .done = &done};
60 struct thread *t =
61 thread_spawn("event_waiter", event_waiter, .arg = &a, .joinable = true);
62 TEST_ASSERT_NONNULL(t);
63
64 wait_for_linked(e: &e, n: 1);
65 TEST_ASSERT(!event_set(&e));
66 thread_join(t);
67
68 /* The signal went to the waiter */
69 TEST_ASSERT(!event_read_state(&e));
70 TEST_ASSERT_EQ(a.result.status, THREAD_WAIT_SATISFIED);
71 TEST_ASSERT_EQ(atomic_load(&done), 1);
72 return TEST_SUCCESS;
73}
74
75TEST_DEFINE_UNIT(event, notification_wakes_all_waiters) {
76 enum { WAITERS = 4 };
77 struct event e;
78 atomic_uint done = 0;
79 struct event_waiter_args args[WAITERS];
80 struct thread *threads[WAITERS];
81 event_init(e: &e, type: EVENT_NOTIFICATION, false);
82
83 for (size_t i = 0; i < WAITERS; i++) {
84 args[i] = (struct event_waiter_args){.e = &e, .done = &done};
85 threads[i] = thread_spawn("event_waiter", event_waiter, .arg = &args[i],
86 .joinable = true);
87 TEST_ASSERT_NONNULL(threads[i]);
88 }
89
90 wait_for_linked(e: &e, n: WAITERS);
91 TEST_ASSERT(!event_set(&e));
92
93 for (size_t i = 0; i < WAITERS; i++) {
94 thread_join(t: threads[i]);
95 TEST_ASSERT_EQ(args[i].result.status, THREAD_WAIT_SATISFIED);
96 }
97
98 TEST_ASSERT_EQ(atomic_load(&done), WAITERS);
99 TEST_ASSERT(event_read_state(&e));
100 TEST_ASSERT(list_head_empty(&e.header.waiters));
101 return TEST_SUCCESS;
102}
103
104TEST_DEFINE_UNIT(event, wait_any_signaled_at_registration) {
105 struct event idle, ready;
106 event_init(e: &idle, type: EVENT_SYNCHRONIZATION, false);
107 event_init(e: &ready, type: EVENT_SYNCHRONIZATION, true);
108
109 const struct thread_wait_object objects[2] = {event_wait_object(e: &idle),
110 event_wait_object(e: &ready)};
111 thread_wait_prepare(objects, count: 2, NULL, type: THREAD_WAIT_UNINTERRUPTIBLE,
112 reason: THREAD_BLOCK_REASON_MANUAL);
113
114 TEST_ASSERT(thread_get_current()->wait_done);
115 struct thread_wait_result r = thread_wait_complete();
116
117 TEST_ASSERT_EQ(r.status, THREAD_WAIT_SATISFIED);
118 TEST_ASSERT_EQ(r.key, 1);
119 TEST_ASSERT(!event_read_state(&ready));
120 TEST_ASSERT(list_head_empty(&idle.header.waiters));
121 TEST_ASSERT(list_head_empty(&ready.header.waiters));
122 TEST_ASSERT_EQ(thread_get_state(thread_get_current()),
123 THREAD_STATE_RUNNING);
124
125 TEST_ASSERT(!event_set(&idle));
126 TEST_ASSERT(event_read_state(&idle));
127 return TEST_SUCCESS;
128}
129
130TEST_DEFINE_UNIT(event, wait_times_out) {
131 struct event e;
132 event_init(e: &e, type: EVENT_NOTIFICATION, false);
133
134 struct thread_wait_result r = event_wait_timeout(
135 e: &e, type: THREAD_WAIT_UNINTERRUPTIBLE, reason: THREAD_BLOCK_REASON_MANUAL, timeout_ms: 5);
136 TEST_ASSERT_EQ(r.status, THREAD_WAIT_TIMED_OUT);
137 TEST_ASSERT_EQ(r.key, THREAD_WAIT_KEY_NONE);
138 TEST_ASSERT_EQ(r.reason, THREAD_WAKE_REASON_SLEEP_TIMEOUT);
139 TEST_ASSERT(list_head_empty(&e.header.waiters));
140
141 event_set(e: &e);
142 r = event_wait_timeout(e: &e, type: THREAD_WAIT_UNINTERRUPTIBLE,
143 reason: THREAD_BLOCK_REASON_MANUAL, timeout_ms: 5);
144 TEST_ASSERT_EQ(r.status, THREAD_WAIT_SATISFIED);
145 return TEST_SUCCESS;
146}
147
148/* Event lives in waiter's stack, TODO: turn into death test */
149struct stack_event_case {
150 atomic(struct event *) published;
151 atomic_uint rounds;
152 atomic_bool stop;
153};
154
155enum { STACK_EVENT_ROUNDS = 500 };
156
157static void stack_event_setter(void *arg) {
158 struct stack_event_case *c = arg;
159
160 while (atomic_load(&c->rounds) < STACK_EVENT_ROUNDS) {
161 struct event *e = atomic_xchg_acq(&c->published, NULL);
162 if (e)
163 event_set(e);
164 else
165 scheduler_yield();
166 }
167}
168
169static void stack_event_round(struct stack_event_case *c,
170 enum event_type type) {
171 struct event e;
172 event_init(e: &e, type, false);
173 atomic_store_release(&c->published, &e);
174
175 event_wait(e: &e, type: THREAD_WAIT_UNINTERRUPTIBLE, reason: THREAD_BLOCK_REASON_MANUAL);
176 memset(&e, 0xAA, sizeof(e));
177}
178
179TEST_DEFINE_UNIT(event, stack_event_lifetime) {
180 struct stack_event_case c = {0};
181 struct thread *setter = thread_spawn("event_setter", stack_event_setter,
182 .arg = &c, .joinable = true);
183 TEST_ASSERT_NONNULL(setter);
184
185 for (size_t i = 0; i < STACK_EVENT_ROUNDS; i++) {
186 stack_event_round(c: &c,
187 type: i & 1 ? EVENT_NOTIFICATION : EVENT_SYNCHRONIZATION);
188 atomic_inc(&c.rounds);
189 }
190
191 thread_join(t: setter);
192 return TEST_SUCCESS;
193}
194