| ... | ... | @@ -16873,29 +16873,37 @@ const parking_futex = struct { |
| 16873 | 16873 | } |
| 16874 | 16874 | |
| 16875 | 16875 | const Bucket = struct { |
| 16876 | | /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no |
| 16877 | | /// waiters. It is important for `wait` to increment this *before* checking the futex value to |
| 16878 | | /// avoid a race. |
| 16879 | | num_waiters: std.atomic.Value(u32), |
| 16880 | | /// Protects `waiters`. |
| 16881 | | mutex: Mutex, |
| 16882 | | waiters: std.DoublyLinkedList, |
| 16883 | | |
| 16884 | | /// Prevent false sharing between buckets. |
| 16885 | | _: void align(std.atomic.cache_line) = {}, |
| 16886 | | |
| 16887 | | const init: Bucket = .{ .num_waiters = .init(0), .mutex = .init, .waiters = .{} }; |
| 16876 | /// The alignment prevents false sharing between buckets. |
| 16877 | waiters: [capacity]?*Waiter align(std.atomic.cache_line) = @splat(null), |
| 16878 | const capacity = std.atomic.cache_line / @sizeOf(?*Waiter); |
| 16879 | |
| 16880 | /// Store the waiter into the bucket, atomic, lock-free. |
| 16881 | fn add(b: *Bucket, w: *Waiter) void { |
| 16882 | while (true) for (&b.waiters) |*slot| { |
| 16883 | if (@cmpxchgWeak(?*Waiter, slot, null, w, .acq_rel, .monotonic) == null) { |
| 16884 | return; |
| 16885 | } |
| 16886 | }; |
| 16887 | } |
| 16888 | |
| 16889 | /// Delete the waiter from the bucket, atomic, lock-free. |
| 16890 | fn remove(b: *Bucket, w: *Waiter) void { |
| 16891 | while (true) for (&b.waiters) |*slot| { |
| 16892 | if (@cmpxchgWeak(?*Waiter, slot, w, null, .acq_rel, .monotonic) == null) { |
| 16893 | return; |
| 16894 | } |
| 16895 | }; |
| 16896 | } |
| 16888 | 16897 | }; |
| 16889 | 16898 | |
| 16890 | 16899 | const Waiter = struct { |
| 16891 | | node: std.DoublyLinkedList.Node, |
| 16900 | node: std.SinglyLinkedList.Node, |
| 16892 | 16901 | address: usize, |
| 16893 | 16902 | tid: std.Thread.Id, |
| 16894 | 16903 | /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread |
| 16895 | 16904 | /// which atomically updates it (to `.none` or `.canceling`) is responsible for: |
| 16896 | 16905 | /// |
| 16897 | 16906 | /// * Removing the `Waiter` from `Bucket.waiters` |
| 16898 | | /// * Decrementing `Bucket.num_waiters` |
| 16899 | 16907 | /// * Atomically setting `done` (after this, the `Waiter` may go out of scope at any time, |
| 16900 | 16908 | /// so must not be referenced again) |
| 16901 | 16909 | /// * Unparking the thread (last, so that the unparked thread definitely sees `done`) |
| ... | ... | @@ -16911,7 +16919,7 @@ const parking_futex = struct { |
| 16911 | 16919 | /// between different futexes. This length seems like it'll provide a reasonable balance |
| 16912 | 16920 | /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line |
| 16913 | 16921 | /// alignment), this uses 32 KiB of memory. |
| 16914 | | var buckets: [256]Bucket = @splat(.init); |
| 16922 | var buckets: [256]Bucket = @splat(.{}); |
| 16915 | 16923 | }; |
| 16916 | 16924 | |
| 16917 | 16925 | // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input |
| ... | ... | @@ -16947,16 +16955,7 @@ const parking_futex = struct { |
| 16947 | 16955 | var status_buf: std.atomic.Value(Thread.Status) = undefined; |
| 16948 | 16956 | |
| 16949 | 16957 | { |
| 16950 | | mutexLockUncancelable(&bucket.mutex); |
| 16951 | | defer mutexUnlock(&bucket.mutex); |
| 16952 | | |
| 16953 | | _ = bucket.num_waiters.fetchAdd(1, .acquire); |
| 16954 | | |
| 16955 | | if (@atomicLoad(u32, ptr, .monotonic) != expect) { |
| 16956 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 16957 | | return; |
| 16958 | | } |
| 16959 | | |
| 16958 | if (@atomicLoad(u32, ptr, .monotonic) != expect) return; |
| 16960 | 16959 | // This is in the critical section to avoid marking the thread as parked until we're |
| 16961 | 16960 | // certain that we're actually going to park. |
| 16962 | 16961 | waiter.thread_status = status: { |
| ... | ... | @@ -16974,11 +16973,7 @@ const parking_futex = struct { |
| 16974 | 16973 | ); |
| 16975 | 16974 | switch (old_status.cancelation) { |
| 16976 | 16975 | .none => {}, // status is now `.parked` |
| 16977 | | .canceling => { |
| 16978 | | // status is now `.canceled` |
| 16979 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 16980 | | return error.Canceled; |
| 16981 | | }, |
| 16976 | .canceling => return error.Canceled, // status is now `.canceled` |
| 16982 | 16977 | .canceled => break :cancelable, // status is still `.canceled` |
| 16983 | 16978 | .parked => unreachable, |
| 16984 | 16979 | .blocked => unreachable, |
| ... | ... | @@ -16996,7 +16991,7 @@ const parking_futex = struct { |
| 16996 | 16991 | break :status &status_buf; |
| 16997 | 16992 | }; |
| 16998 | 16993 | |
| 16999 | | bucket.waiters.append(&waiter.node); |
| 16994 | bucket.add(&waiter); |
| 17000 | 16995 | } |
| 17001 | 16996 | |
| 17002 | 16997 | const deadline: ?Io.Clock.Timestamp = switch (timeout) { |
| ... | ... | @@ -17017,10 +17012,7 @@ const parking_futex = struct { |
| 17017 | 17012 | .parked => { |
| 17018 | 17013 | // We saw a timeout and updated our own status from `.parked` to `.none`. It is |
| 17019 | 17014 | // our responsibility to remove `waiter` from `bucket`. |
| 17020 | | mutexLockUncancelable(&bucket.mutex); |
| 17021 | | defer mutexUnlock(&bucket.mutex); |
| 17022 | | bucket.waiters.remove(&waiter.node); |
| 17023 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17015 | bucket.remove(&waiter); |
| 17024 | 17016 | }, |
| 17025 | 17017 | .none, .canceling => { |
| 17026 | 17018 | // Race condition: the timeout was reached, then `wake` or a cancelation tried |
| ... | ... | @@ -17046,25 +17038,16 @@ const parking_futex = struct { |
| 17046 | 17038 | |
| 17047 | 17039 | const bucket = bucketForAddress(@intFromPtr(ptr)); |
| 17048 | 17040 | |
| 17049 | | // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release` |
| 17050 | | // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw. |
| 17051 | | if (bucket.num_waiters.fetchAdd(0, .release) == 0) { |
| 17052 | | @branchHint(.likely); |
| 17053 | | return; // no waiters |
| 17054 | | } |
| 17055 | | |
| 17056 | 17041 | // Waiters removed from the linked list under the mutex so we can unpark their threads outside |
| 17057 | 17042 | // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. |
| 17058 | | var waking_head: ?*std.DoublyLinkedList.Node = null; |
| 17043 | var waking_head: ?*std.SinglyLinkedList.Node = null; |
| 17059 | 17044 | { |
| 17060 | | mutexLockUncancelable(&bucket.mutex); |
| 17061 | | defer mutexUnlock(&bucket.mutex); |
| 17062 | | |
| 17063 | 17045 | var num_removed: u32 = 0; |
| 17064 | | var it = bucket.waiters.first; |
| 17065 | | while (num_removed < max_waiters) { |
| 17066 | | const waiter: *Waiter = @fieldParentPtr("node", it orelse break); |
| 17067 | | it = waiter.node.next; |
| 17046 | var i: usize = 0; |
| 17047 | while (num_removed < max_waiters) : (i += 1) { |
| 17048 | const waiter: *Waiter = while (bucket.waiters.len - i != 0) : (i += 1) { |
| 17049 | break @atomicLoad(?*Waiter, &bucket.waiters[i], .monotonic) orelse continue; |
| 17050 | } else break; |
| 17068 | 17051 | if (waiter.address != @intFromPtr(ptr)) continue; |
| 17069 | 17052 | const old_status = waiter.thread_status.fetchAnd( |
| 17070 | 17053 | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| ... | ... | @@ -17072,7 +17055,7 @@ const parking_futex = struct { |
| 17072 | 17055 | ); |
| 17073 | 17056 | switch (old_status.cancelation) { |
| 17074 | 17057 | .parked => {}, // state updated to `.none` |
| 17075 | | .none => continue, // race with timeout; they are about to lock `bucket.mutex` and remove themselves from the bucket |
| 17058 | .none => continue, // race with timeout; they are about to remove themselves from the bucket |
| 17076 | 17059 | .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet |
| 17077 | 17060 | .canceled => unreachable, |
| 17078 | 17061 | .blocked => unreachable, |
| ... | ... | @@ -17081,13 +17064,11 @@ const parking_futex = struct { |
| 17081 | 17064 | .blocked_canceling => unreachable, |
| 17082 | 17065 | } |
| 17083 | 17066 | // We're waking this waiter. Remove them from the bucket and add them to our local list. |
| 17084 | | bucket.waiters.remove(&waiter.node); |
| 17067 | @atomicStore(?*Waiter, &bucket.waiters[i], null, .release); |
| 17085 | 17068 | waiter.node.next = waking_head; |
| 17086 | 17069 | waking_head = &waiter.node; |
| 17087 | 17070 | num_removed += 1; |
| 17088 | 17071 | } |
| 17089 | | |
| 17090 | | _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); |
| 17091 | 17072 | } |
| 17092 | 17073 | |
| 17093 | 17074 | var unpark_buf: [128]UnparkTid = undefined; |
| ... | ... | @@ -17113,10 +17094,7 @@ const parking_futex = struct { |
| 17113 | 17094 | |
| 17114 | 17095 | fn removeCanceledWaiter(waiter: *Waiter) void { |
| 17115 | 17096 | const bucket = bucketForAddress(waiter.address); |
| 17116 | | mutexLockUncancelable(&bucket.mutex); |
| 17117 | | defer mutexUnlock(&bucket.mutex); |
| 17118 | | bucket.waiters.remove(&waiter.node); |
| 17119 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17097 | bucket.remove(waiter); |
| 17120 | 17098 | waiter.done.store(true, .release); // potentially invalidates `waiter.*` |
| 17121 | 17099 | } |
| 17122 | 17100 | }; |