| 1 | #include <string.h> |
| 2 | #include <sync/event.h> |
| 3 | |
| 4 | #include "sync/tests/test_internal.h" |
| 5 | |
| 6 | TEST_GROUP_DEFINE(event); |
| 7 | |
| 8 | static 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 | |
| 20 | static void wait_for_linked(struct event *e, size_t n) { |
| 21 | while (linked_waiters(e) < n) |
| 22 | scheduler_yield(); |
| 23 | } |
| 24 | |
| 25 | struct event_waiter_args { |
| 26 | struct event *e; |
| 27 | atomic_uint *done; |
| 28 | struct thread_wait_result result; |
| 29 | }; |
| 30 | |
| 31 | static 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 | |
| 38 | TEST_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 | |
| 54 | TEST_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 | |
| 75 | TEST_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 | |
| 104 | TEST_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 | |
| 130 | TEST_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 */ |
| 149 | struct stack_event_case { |
| 150 | atomic(struct event *) published; |
| 151 | atomic_uint rounds; |
| 152 | atomic_bool stop; |
| 153 | }; |
| 154 | |
| 155 | enum { STACK_EVENT_ROUNDS = 500 }; |
| 156 | |
| 157 | static 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 | |
| 169 | static 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 | |
| 179 | TEST_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 | |