| ... | ... | @@ -43,99 +43,132 @@ pub const AutoResetEvent = struct { |
| 43 | 43 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); |
| 44 | 44 | |
| 45 | 45 | pub fn wait(self: *AutoResetEvent) void { |
| 46 | | self.waitInner(null) catch unreachable; |
| 46 | self.waitFor(null) catch unreachable; |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { |
| 50 | | return self.waitInner(timeout); |
| 50 | return self.waitFor(timeout); |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | | fn waitInner(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { |
| 54 | | // the local ResetEvent is lazily initialized |
| 55 | | var has_reset_event = false; |
| 53 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { |
| 54 | // lazily initialized std.ResetEvent |
| 56 | 55 | var reset_event: std.ResetEvent align(event_align) = undefined; |
| 56 | var has_reset_event = false; |
| 57 | 57 | defer if (has_reset_event) { |
| 58 | 58 | reset_event.deinit(); |
| 59 | 59 | }; |
| 60 | 60 | |
| 61 | 61 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 62 | 62 | while (true) { |
| 63 | | switch (state) { |
| 64 | | UNSET => { |
| 65 | | if (!has_reset_event) { |
| 66 | | has_reset_event = true; |
| 67 | | reset_event = std.ResetEvent.init(); |
| 68 | | } |
| 69 | | state = @cmpxchgWeak( |
| 70 | | usize, |
| 71 | | &self.state, |
| 72 | | state, |
| 73 | | @ptrToInt(&reset_event), |
| 74 | | .SeqCst, |
| 75 | | .SeqCst, |
| 76 | | ) orelse { |
| 77 | | if (timeout) |timeout_ns| { |
| 78 | | reset_event.timedWait(timeout_ns) catch { |
| 79 | | state = @cmpxchgStrong( |
| 80 | | usize, |
| 81 | | &self.state, |
| 82 | | @ptrToInt(&reset_event), |
| 83 | | UNSET, |
| 84 | | .SeqCst, |
| 85 | | .SeqCst, |
| 86 | | ) orelse return error.TimedOut; |
| 87 | | assert(state == SET); |
| 88 | | reset_event.wait(); |
| 89 | | }; |
| 90 | | } else { |
| 91 | | reset_event.wait(); |
| 92 | | } |
| 93 | | return; |
| 94 | | }; |
| 95 | | }, |
| 96 | | SET => { |
| 97 | | @atomicStore(usize, &self.state, UNSET, .SeqCst); |
| 98 | | return; |
| 99 | | }, |
| 100 | | else => { |
| 101 | | unreachable; // multiple waiters on the same Event |
| 102 | | } |
| 63 | // consume a notification if there is any |
| 64 | if (state == SET) { |
| 65 | @atomicStore(usize, &self.state, UNSET, .SeqCst); |
| 66 | return; |
| 67 | } |
| 68 | |
| 69 | // check if theres currently a pending ResetEvent pointer already registered |
| 70 | if (state != UNSET) { |
| 71 | unreachable; // multiple waiting threads on the same AutoResetEvent |
| 103 | 72 | } |
| 73 | |
| 74 | // lazily initialize the ResetEvent if it hasn't been already |
| 75 | if (!has_reset_event) { |
| 76 | has_reset_event = true; |
| 77 | reset_event = std.ResetEvent.init(); |
| 78 | } |
| 79 | |
| 80 | // Since the AutoResetEvent currently isnt set, |
| 81 | // try to register our ResetEvent on it to wait |
| 82 | // for a set() call from another thread. |
| 83 | if (@cmpxchgWeak( |
| 84 | usize, |
| 85 | &self.state, |
| 86 | UNSET, |
| 87 | @ptrToInt(&reset_event), |
| 88 | .SeqCst, |
| 89 | .SeqCst, |
| 90 | )) |new_state| { |
| 91 | state = new_state; |
| 92 | continue; |
| 93 | } |
| 94 | |
| 95 | // if no timeout was specified, then just wait forever |
| 96 | const timeout_ns = timeout orelse { |
| 97 | reset_event.wait(); |
| 98 | return; |
| 99 | }; |
| 100 | |
| 101 | // wait with a timeout and return if signalled via set() |
| 102 | if (reset_event.timedWait(timeout_ns)) |_| { |
| 103 | return; |
| 104 | } else |timed_out| {} |
| 105 | |
| 106 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. |
| 107 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. |
| 108 | state = @cmpxchgStrong( |
| 109 | usize, |
| 110 | &self.state, |
| 111 | @ptrToInt(&reset_event), |
| 112 | UNSET, |
| 113 | .SeqCst, |
| 114 | .SeqCst, |
| 115 | ) orelse return error.TimedOut; |
| 116 | |
| 117 | // We didn't manage to unregister ourselves from the state. |
| 118 | if (state == SET) { |
| 119 | unreachable; // AutoResetEvent notified without waking up the waiting thread |
| 120 | } else if (state != UNSET) { |
| 121 | unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out |
| 122 | } |
| 123 | |
| 124 | // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. |
| 125 | // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. |
| 126 | // We don't return error.TimedOut here as it technically notified us while we were "timing out". |
| 127 | reset_event.wait(); |
| 128 | return; |
| 104 | 129 | } |
| 105 | 130 | } |
| 106 | 131 | |
| 107 | 132 | pub fn set(self: *AutoResetEvent) void { |
| 108 | 133 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 109 | 134 | while (true) { |
| 110 | | switch (state) { |
| 111 | | UNSET => { |
| 112 | | state = @cmpxchgWeak( |
| 113 | | usize, |
| 114 | | &self.state, |
| 115 | | state, |
| 116 | | SET, |
| 117 | | .SeqCst, |
| 118 | | .SeqCst, |
| 119 | | ) orelse return; |
| 120 | | }, |
| 121 | | SET => { |
| 122 | | return; |
| 123 | | }, |
| 124 | | else => |reset_event_ptr| { |
| 125 | | state = @cmpxchgWeak( |
| 126 | | usize, |
| 127 | | &self.state, |
| 128 | | state, |
| 129 | | UNSET, |
| 130 | | .SeqCst, |
| 131 | | .SeqCst, |
| 132 | | ) orelse { |
| 133 | | const reset_event = @intToPtr(*align(event_align) std.ResetEvent, reset_event_ptr); |
| 134 | | reset_event.set(); |
| 135 | | return; |
| 136 | | }; |
| 137 | | } |
| 135 | // If the AutoResetEvent is already set, there is nothing else left to do |
| 136 | if (state == SET) { |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | // If the AutoResetEvent isn't set, |
| 141 | // then try to leave a notification for the wait() thread that we set() it. |
| 142 | if (state == UNSET) { |
| 143 | state = @cmpxchgWeak( |
| 144 | usize, |
| 145 | &self.state, |
| 146 | UNSET, |
| 147 | SET, |
| 148 | .SeqCst, |
| 149 | .SeqCst, |
| 150 | ) orelse return; |
| 151 | continue; |
| 138 | 152 | } |
| 153 | |
| 154 | // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. |
| 155 | // Try to acquire ownership of it so that we can wake it up. |
| 156 | // This also resets the AutoResetEvent so that there is no race condition as defined above. |
| 157 | if (@cmpxchgWeak( |
| 158 | usize, |
| 159 | &self.state, |
| 160 | state, |
| 161 | UNSET, |
| 162 | .SeqCst, |
| 163 | .SeqCst, |
| 164 | )) |new_state| { |
| 165 | state = new_state; |
| 166 | continue; |
| 167 | } |
| 168 | |
| 169 | const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state); |
| 170 | reset_event.set(); |
| 171 | return; |
| 139 | 172 | } |
| 140 | 173 | } |
| 141 | 174 | }; |
| ... | ... | @@ -161,7 +194,6 @@ test "std.AutoResetEvent" { |
| 161 | 194 | const Self = @This(); |
| 162 | 195 | |
| 163 | 196 | fn sender(self: *Self) void { |
| 164 | | std.debug.print("\n", .{}); |
| 165 | 197 | testing.expect(self.value == 0); |
| 166 | 198 | self.value = 1; |
| 167 | 199 | self.out.set(); |