| 1 | #include <thread/io_wait.h> |
| 2 | |
| 3 | #include "thread/tests/test_internal.h" |
| 4 | |
| 5 | TEST_GROUP_DEFINE(wait_block); |
| 6 | |
| 7 | TEST_DEFINE_UNIT(wait_block, caller_storage_wait_any) { |
| 8 | struct thread_wait_header [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 | |
| 30 | struct wait_racer { |
| 31 | struct thread_wait_header *; |
| 32 | atomic_bool *go; |
| 33 | atomic_uint *wins; |
| 34 | }; |
| 35 | |
| 36 | static 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 | |
| 45 | TEST_DEFINE_UNIT(wait_block, mt_wait_any) { |
| 46 | struct thread_wait_header [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 | |
| 74 | struct apc_wait_case { |
| 75 | struct thread_wait_header ; |
| 76 | atomic_bool armed; |
| 77 | atomic_bool lost_registration; |
| 78 | atomic_uint delivered; |
| 79 | uint64_t epoch; |
| 80 | }; |
| 81 | |
| 82 | static 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 | |
| 99 | static 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 | |
| 108 | TEST_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 | |
| 131 | TEST_DEFINE_UNIT(wait_block, uninterruptible_latches_alert) { |
| 132 | struct thread_wait_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 | |
| 150 | TEST_DEFINE_UNIT(wait_block, stale_timeout_cannot_satisfy_next_wait) { |
| 151 | struct thread_wait_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 | |