| 1 | #pragma once |
| 2 | #include <structures/list.h> |
| 3 | #include <sync/spinlock.h> |
| 4 | #include <thread/thread_types.h> |
| 5 | #include <time/time.h> |
| 6 | |
| 7 | #define THREAD_WAIT_BLOCK_SYNC 0 |
| 8 | #define THREAD_NUM_WAIT_BLOCKS 4 |
| 9 | #define THREAD_WAIT_KEY_NONE UINT16_MAX |
| 10 | |
| 11 | struct { |
| 12 | struct spinlock ; |
| 13 | struct list_head ; |
| 14 | }; |
| 15 | |
| 16 | #define (name_) \ |
| 17 | {.lock = SPINLOCK_INIT, .waiters = LIST_HEAD_INIT((name_).waiters)} |
| 18 | |
| 19 | enum thread_wait_block_state { |
| 20 | THREAD_WAIT_BLOCK_INACTIVE, |
| 21 | THREAD_WAIT_BLOCK_ACTIVE, |
| 22 | THREAD_WAIT_BLOCK_SATISFIED, |
| 23 | }; |
| 24 | |
| 25 | struct thread_wait_block { |
| 26 | /* TODO: we're using a doubly linked list here because the block list under |
| 27 | * a thread can grow arbitrarily long. Perhaps someday we can consider |
| 28 | * something like Windows' MAXIMUM_WAIT_OBJECTS, so we can go for |
| 29 | * a singularly linked list, save 8 bytes, at the cost of an |
| 30 | * absolute maximum + maybe minor slowdowns (?) */ |
| 31 | struct list_node obj_node; |
| 32 | struct thread *thread; |
| 33 | void *object; |
| 34 | uint16_t key; |
| 35 | enum thread_wait_block_state state; |
| 36 | struct thread_wait_header *; |
| 37 | }; |
| 38 | ct_assert_struct_size_eq(thread_wait_block, 48); |
| 39 | |
| 40 | struct thread_wait_object { |
| 41 | struct thread_wait_header *; |
| 42 | void *object; |
| 43 | |
| 44 | /* Optional function. This runs under header->lock right before |
| 45 | * we link the wait block, and if it returns true, |
| 46 | * the wait is satisfied at registration time and any |
| 47 | * remaining objects never get linked */ |
| 48 | bool (*try_acquire)(void *object); |
| 49 | }; |
| 50 | |
| 51 | struct thread_wait_result { |
| 52 | enum thread_wait_status status; |
| 53 | uint16_t key; |
| 54 | enum thread_resume_reason reason; |
| 55 | }; |
| 56 | |
| 57 | void (struct thread_wait_header *); |
| 58 | void thread_wait_init(struct thread *thread); |
| 59 | |
| 60 | /* If storage == NULL, we use the thread's embedded blocks. No allocation |
| 61 | * ever happens in here, however, the caller remains runnable */ |
| 62 | void thread_wait_prepare(const struct thread_wait_object *objects, size_t count, |
| 63 | struct thread_wait_block *storage, |
| 64 | enum thread_wait_type type, |
| 65 | enum thread_block_reason reason); |
| 66 | |
| 67 | void thread_wait_prepare_one(struct thread_wait_header *, void *object, |
| 68 | enum thread_wait_type type, |
| 69 | enum thread_block_reason reason); |
| 70 | |
| 71 | void thread_wait_prepare_to_sleep(struct thread_wait_header *, |
| 72 | void *object, enum thread_wait_type type); |
| 73 | |
| 74 | /* Satisfy unlinks ALL blocks before the waiter becomes runnable. |
| 75 | * The callback runs just for the winner */ |
| 76 | bool thread_wait_satisfy(struct thread_wait_block *block, |
| 77 | enum thread_resume_reason reason, |
| 78 | void (*callback)(struct thread *)); |
| 79 | |
| 80 | /* For callbacks that took a snapshot of thread->wait_epoch */ |
| 81 | bool thread_wait_satisfy_epoch(struct thread_wait_block *block, uint64_t epoch, |
| 82 | enum thread_resume_reason reason, |
| 83 | void (*cb)(struct thread *)); |
| 84 | |
| 85 | struct thread *(struct thread_wait_header *hdr, |
| 86 | enum thread_resume_reason reason, |
| 87 | void (*cb)(struct thread *)); |
| 88 | |
| 89 | /* Like thread_wait_header_satisfy, but if nothing remains |
| 90 | * to satisfy, empty_locked runs under hdr->lock */ |
| 91 | struct thread *(struct thread_wait_header *hdr, |
| 92 | enum thread_resume_reason reason, |
| 93 | void (*cb)(struct thread *), |
| 94 | void (*empty_locked)(void *), |
| 95 | void *arg); |
| 96 | |
| 97 | /* Satisfies the waiters linked when before_locked runs, under hdr->lock */ |
| 98 | size_t (struct thread_wait_header *hdr, |
| 99 | enum thread_resume_reason reason, |
| 100 | void (*callback)(struct thread *), |
| 101 | void (*before_locked)(void *), void *arg); |
| 102 | |
| 103 | /* Alert is a "level-triggered" singular event. We only end interruptible |
| 104 | * waits with alerts, but otherwise, the alert will merely pend until |
| 105 | * the next interruptible wait/park happens */ |
| 106 | void thread_alert(struct thread *cc_nonnull_ptr thread); |
| 107 | void thread_park(void); |
| 108 | |
| 109 | /* The actual "go wait" action, yield and whatnot */ |
| 110 | struct thread_wait_result thread_wait_complete(void); |
| 111 | |
| 112 | struct thread_wait_result thread_wait_complete_timeout(time_ms_t timeout_ms); |
| 113 | void thread_wait_cancel(void); |
| 114 | |