1#include <thread/io_wait.h>
2
3#include "thread/tests/test_internal.h"
4
5TEST_GROUP_DEFINE(wait_block);
6
7TEST_DEFINE_UNIT(wait_block, caller_storage_wait_any) {
8 struct thread_wait_header headers[6];
9 struct thread_wait_object objects[6];
10 struct thread_wait_block blocks[6];
11 for (size_t i = 0; i < 6; i++) {
12 thread_wait_header_init(header: &headers[i]);
13 objects[i] = (struct thread_wait_object){.header = &headers[i],
14 .object = &headers[i]};
15 }
16 thread_wait_prepare(objects, count: 6, storage: blocks, type: THREAD_WAIT_UNINTERRUPTIBLE,
17 reason: THREAD_BLOCK_REASON_MANUAL);
18 TEST_ASSERT_EQ(thread_get_current()->active_wait_blocks, &blocks[0]);
19 TEST_ASSERT(thread_wait_satisfy(&blocks[4],
20 THREAD_WAKE_REASON_BLOCKING_MANUAL, NULL));
21 for (size_t i = 0; i < 6; i++) {
22 TEST_ASSERT(list_head_empty(&headers[i].waiters));
23 TEST_ASSERT_EQ(blocks[i].state, i == 4 ? THREAD_WAIT_BLOCK_SATISFIED
24 : THREAD_WAIT_BLOCK_INACTIVE);
25 }
26 TEST_ASSERT_EQ(thread_wait_complete().key, 4);
27 return TEST_SUCCESS;
28}
29
30struct wait_racer {
31 struct thread_wait_header *header;
32 atomic_bool *go;
33 atomic_uint *wins;
34};
35
36static void race_satisfy(void *arg) {
37 struct wait_racer *r = arg;
38 while (!atomic_load(r->go))
39 scheduler_yield();
40 if (thread_wait_header_satisfy(hdr: r->header,
41 reason: THREAD_WAKE_REASON_BLOCKING_MANUAL, NULL))
42 atomic_inc(r->wins);
43}
44
45TEST_DEFINE_UNIT(wait_block, mt_wait_any) {
46 struct thread_wait_header headers[2];
47 struct thread_wait_object objects[2];
48 struct wait_racer racers[2];
49 struct thread *wakers[2];
50 atomic_bool go = false;
51 atomic_uint wins = 0;
52 for (size_t i = 0; i < 2; i++) {
53 thread_wait_header_init(header: &headers[i]);
54 objects[i] = (struct thread_wait_object){.header = &headers[i],
55 .object = &headers[i]};
56 racers[i] = (struct wait_racer){&headers[i], &go, &wins};
57 wakers[i] = thread_spawn("wait_racer", race_satisfy, .arg = &racers[i],
58 .joinable = true);
59 enforce(wakers[i]);
60 }
61 thread_wait_prepare(objects, count: 2, NULL, type: THREAD_WAIT_UNINTERRUPTIBLE,
62 reason: THREAD_BLOCK_REASON_MANUAL);
63 atomic_store(&go, true);
64 uint16_t key = thread_wait_complete().key;
65 for (size_t i = 0; i < 2; i++)
66 thread_join(t: wakers[i]);
67 TEST_ASSERT_LT(key, 2);
68 TEST_ASSERT_EQ(atomic_load(&wins), 1);
69 TEST_ASSERT(list_head_empty(&headers[0].waiters));
70 TEST_ASSERT(list_head_empty(&headers[1].waiters));
71 return TEST_SUCCESS;
72}
73
74struct apc_wait_case {
75 struct thread_wait_header header;
76 atomic_bool armed;
77 atomic_bool lost_registration;
78 atomic_uint delivered;
79 uint64_t epoch;
80};
81
82static void satisfy_during_apc(struct apc_context *ac, void *arg) {
83 cc_ignore(ac);
84 struct apc_wait_case *c = arg;
85 struct thread *t = thread_get_current();
86 enum irql irql = spin_lock_high(&t->wait_lock);
87 if (t->active_wait_blocks != t->wait_blocks ||
88 atomic_load_rlx(&t->wait_epoch) != c->epoch ||
89 t->wait_blocks[0].state != THREAD_WAIT_BLOCK_ACTIVE || t->alert_pending)
90 atomic_store(&c->lost_registration, true);
91 spin_unlock(&t->wait_lock, irql);
92 /* Four APCs must return to the SAME registration, and fifth completes
93 * it from inside delivery */
94 if (atomic_fetch_add(&c->delivered, 1) == 4)
95 thread_wait_header_satisfy(hdr: &c->header,
96 reason: THREAD_WAKE_REASON_BLOCKING_MANUAL, NULL);
97}
98
99static void apc_waiter(void *arg) {
100 struct apc_wait_case *c = arg;
101 thread_wait_prepare_one(header: &c->header, object: c, type: THREAD_WAIT_INTERRUPTIBLE,
102 reason: THREAD_BLOCK_REASON_MANUAL);
103 c->epoch = atomic_load_rlx(&thread_get_current()->wait_epoch);
104 atomic_store(&c->armed, true);
105 thread_wait_complete();
106}
107
108TEST_DEFINE_UNIT(wait_block, apc_preserves_registration) {
109 struct apc_wait_case c = {0};
110 thread_wait_header_init(header: &c.header);
111 struct apc apc;
112 apc_init(a: &apc, fn: satisfy_during_apc, arg1: &c, NULL);
113 struct thread *t =
114 thread_spawn("apc_waiter", apc_waiter, .arg = &c, .joinable = true);
115 TEST_ASSERT_NONNULL(t);
116 while (!atomic_load(&c.armed))
117 scheduler_yield();
118 for (size_t i = 0; i < 5; i++) {
119 enforce(apc_enqueue(t, &apc, APC_TYPE_KERNEL));
120 while (atomic_load_acq(&apc.state) != APC_STATE_IDLE)
121 scheduler_yield();
122 }
123 thread_join(t);
124 apc_putref(a: &apc);
125 TEST_ASSERT(!atomic_load(&c.lost_registration));
126 TEST_ASSERT_EQ(atomic_load(&c.delivered), 5);
127 TEST_ASSERT(list_head_empty(&c.header.waiters));
128 return TEST_SUCCESS;
129}
130
131TEST_DEFINE_UNIT(wait_block, uninterruptible_latches_alert) {
132 struct thread_wait_header header;
133 struct thread *t = thread_get_current();
134 thread_wait_header_init(header: &header);
135 thread_wait_prepare_one(header: &header, object: &header, type: THREAD_WAIT_UNINTERRUPTIBLE,
136 reason: THREAD_BLOCK_REASON_MANUAL);
137 thread_alert(thread: t);
138 bool linked = !list_head_empty(h: &header.waiters);
139 thread_wait_satisfy(block: &t->wait_blocks[0], reason: THREAD_WAKE_REASON_SLEEP_MANUAL,
140 NULL);
141 struct thread_wait_result result = thread_wait_complete();
142 TEST_ASSERT_EQ(result.status, THREAD_WAIT_SATISFIED);
143 TEST_ASSERT(linked);
144 TEST_ASSERT(t->alert_pending);
145 thread_park(); /* Consumes the latched permit without blocking */
146 TEST_ASSERT(!t->alert_pending);
147 return TEST_SUCCESS;
148}
149
150TEST_DEFINE_UNIT(wait_block, stale_timeout_cannot_satisfy_next_wait) {
151 struct thread_wait_header header;
152 struct thread *t = thread_get_current();
153 thread_wait_header_init(header: &header);
154 thread_wait_prepare_one(header: &header, object: &header, type: THREAD_WAIT_UNINTERRUPTIBLE,
155 reason: THREAD_BLOCK_REASON_MANUAL);
156 uint64_t old_epoch = atomic_load_rlx(&t->wait_epoch);
157 thread_wait_cancel();
158 thread_wait_prepare_one(header: &header, object: &header, type: THREAD_WAIT_UNINTERRUPTIBLE,
159 reason: THREAD_BLOCK_REASON_MANUAL);
160 bool stale_won = thread_wait_satisfy_epoch(
161 block: &t->wait_blocks[0], epoch: old_epoch, reason: THREAD_WAKE_REASON_SLEEP_TIMEOUT, NULL);
162 thread_wait_satisfy(block: &t->wait_blocks[0], reason: THREAD_WAKE_REASON_SLEEP_MANUAL,
163 NULL);
164 struct thread_wait_result result = thread_wait_complete();
165 TEST_ASSERT(!stale_won);
166 TEST_ASSERT_EQ(result.reason, THREAD_WAKE_REASON_SLEEP_MANUAL);
167 return TEST_SUCCESS;
168}
169