| ... | @@ -43,99 +43,132 @@ pub const AutoResetEvent = struct { | ... | @@ -43,99 +43,132 @@ pub const AutoResetEvent = struct { |
| 43 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); | 43 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); |
| 44 | | 44 | |
| 45 | pub fn wait(self: *AutoResetEvent) void { | 45 | pub fn wait(self: *AutoResetEvent) void { |
| 46 | self.waitInner(null) catch unreachable; | 46 | self.waitFor(null) catch unreachable; |
| 47 | } | 47 | } |
| 48 | | 48 | |
| 49 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { | 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 { | 53 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { |
| 54 | // the local ResetEvent is lazily initialized | 54 | // lazily initialized std.ResetEvent |
| 55 | var has_reset_event = false; | | |
| 56 | var reset_event: std.ResetEvent align(event_align) = undefined; | 55 | var reset_event: std.ResetEvent align(event_align) = undefined; |
| | 56 | var has_reset_event = false; |
| 57 | defer if (has_reset_event) { | 57 | defer if (has_reset_event) { |
| 58 | reset_event.deinit(); | 58 | reset_event.deinit(); |
| 59 | }; | 59 | }; |
| 60 | | 60 | |
| 61 | var state = @atomicLoad(usize, &self.state, .SeqCst); | 61 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 62 | while (true) { | 62 | while (true) { |
| 63 | switch (state) { | 63 | // consume a notification if there is any |
| 64 | UNSET => { | 64 | if (state == SET) { |
| 65 | if (!has_reset_event) { | 65 | @atomicStore(usize, &self.state, UNSET, .SeqCst); |
| 66 | has_reset_event = true; | 66 | return; |
| 67 | reset_event = std.ResetEvent.init(); | 67 | } |
| 68 | } | 68 | |
| 69 | state = @cmpxchgWeak( | 69 | // check if theres currently a pending ResetEvent pointer already registered |
| 70 | usize, | 70 | if (state != UNSET) { |
| 71 | &self.state, | 71 | unreachable; // multiple waiting threads on the same AutoResetEvent |
| 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 | } | | |
| 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 | pub fn set(self: *AutoResetEvent) void { | 132 | pub fn set(self: *AutoResetEvent) void { |
| 108 | var state = @atomicLoad(usize, &self.state, .SeqCst); | 133 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 109 | while (true) { | 134 | while (true) { |
| 110 | switch (state) { | 135 | // If the AutoResetEvent is already set, there is nothing else left to do |
| 111 | UNSET => { | 136 | if (state == SET) { |
| 112 | state = @cmpxchgWeak( | 137 | return; |
| 113 | usize, | 138 | } |
| 114 | &self.state, | 139 | |
| 115 | state, | 140 | // If the AutoResetEvent isn't set, |
| 116 | SET, | 141 | // then try to leave a notification for the wait() thread that we set() it. |
| 117 | .SeqCst, | 142 | if (state == UNSET) { |
| 118 | .SeqCst, | 143 | state = @cmpxchgWeak( |
| 119 | ) orelse return; | 144 | usize, |
| 120 | }, | 145 | &self.state, |
| 121 | SET => { | 146 | UNSET, |
| 122 | return; | 147 | SET, |
| 123 | }, | 148 | .SeqCst, |
| 124 | else => |reset_event_ptr| { | 149 | .SeqCst, |
| 125 | state = @cmpxchgWeak( | 150 | ) orelse return; |
| 126 | usize, | 151 | continue; |
| 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 | } | | |
| 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,7 +194,6 @@ test "std.AutoResetEvent" { |
| 161 | const Self = @This(); | 194 | const Self = @This(); |
| 162 | | 195 | |
| 163 | fn sender(self: *Self) void { | 196 | fn sender(self: *Self) void { |
| 164 | std.debug.print("\n", .{}); | | |
| 165 | testing.expect(self.value == 0); | 197 | testing.expect(self.value == 0); |
| 166 | self.value = 1; | 198 | self.value = 1; |
| 167 | self.out.set(); | 199 | self.out.set(); |