authorgravatar for 45520026+kprotty@users.noreply.github.comkprotty <45520026+kprotty@users.noreply.github.com> 2020-10-11 14:17:51-05:00
committergravatar for 45520026+kprotty@users.noreply.github.comkprotty <45520026+kprotty@users.noreply.github.com> 2020-10-11 14:17:51-05:00
loga42c0f88e0749a1b17e52bb93d6d31b9dfbca37e
tree434b4862697d2afc0e3ae5113f51a68eaf69b417
parent53c63bdb73d9fbc5a54afb4977bb975b03c4c9cc

AutoResetEvent


2 files changed, 198 insertions(+), 0 deletions(-)

lib/std/auto_reset_event.zig created+197
...@@ -0,0 +1,197 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std.zig");
7const builtin = @import("builtin");
8const testing = std.testing;
9const assert = std.debug.assert;
10
11/// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`.
12/// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like).
13pub const AutoResetEvent = struct {
14 // AutoResetEvent has 3 possible states:
15 // - UNSET: the AutoResetEvent is currently unset
16 // - SET: the AutoResetEvent was notified before a wait() was called
17 // - <std.ResetEvent pointer>: there is an active waiter waiting for a notification.
18 //
19 // When attempting to wait:
20 // if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
21 // if the event is already set, then it consumes the notification and resets the event.
22 //
23 // When attempting to notify:
24 // if the event is unset, then we set the event
25 // if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
26 //
27 // This ensures that the event is automatically reset after a wait() has been issued
28 // and avoids the race condition when using std.ResetEvent in the following scenario:
29 // thread 1 | thread 2
30 // std.ResetEvent.wait() |
31 // | std.ResetEvent.set()
32 // | std.ResetEvent.set()
33 // std.ResetEvent.reset() |
34 // std.ResetEvent.wait() | (missed the second .set() notification above)
35
36
37 state: usize = UNSET,
38
39 const UNSET = 0;
40 const SET = 1;
41
42 // the minimum alignment for the `*std.ResetEvent` created by wait*()
43 const event_align = std.math.max(@alignOf(std.ResetEvent), 2);
44
45 pub fn wait(self: *AutoResetEvent) void {
46 self.waitInner(null) catch unreachable;
47 }
48
49 pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void {
50 return self.waitInner(timeout);
51 }
52
53 fn waitInner(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
54 // the local ResetEvent is lazily initialized
55 var has_reset_event = false;
56 var reset_event: std.ResetEvent align(event_align) = undefined;
57 defer if (has_reset_event) {
58 reset_event.deinit();
59 };
60
61 var state = @atomicLoad(usize, &self.state, .SeqCst);
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 }
103 }
104 }
105 }
106
107 pub fn set(self: *AutoResetEvent) void {
108 var state = @atomicLoad(usize, &self.state, .SeqCst);
109 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 }
138 }
139 }
140 }
141};
142
143test "std.AutoResetEvent" {
144 // test local code paths
145 {
146 var event = AutoResetEvent{};
147 testing.expectError(error.TimedOut, event.timedWait(1));
148 event.set();
149 event.wait();
150 }
151
152 // test cross-thread signaling
153 if (builtin.single_threaded)
154 return;
155
156 const Context = struct {
157 value: u128 = 0,
158 in: AutoResetEvent = AutoResetEvent{},
159 out: AutoResetEvent = AutoResetEvent{},
160
161 const Self = @This();
162
163 fn sender(self: *Self) void {
164 std.debug.print("\n", .{});
165 testing.expect(self.value == 0);
166 self.value = 1;
167 self.out.set();
168
169 self.in.wait();
170 testing.expect(self.value == 2);
171 self.value = 3;
172 self.out.set();
173
174 self.in.wait();
175 testing.expect(self.value == 4);
176 }
177
178 fn receiver(self: *Self) void {
179 self.out.wait();
180 testing.expect(self.value == 1);
181 self.value = 2;
182 self.in.set();
183
184 self.out.wait();
185 testing.expect(self.value == 3);
186 self.value = 4;
187 self.in.set();
188 }
189 };
190
191 var context = Context{};
192 const send_thread = try std.Thread.spawn(&context, Context.sender);
193 const recv_thread = try std.Thread.spawn(&context, Context.receiver);
194
195 send_thread.wait();
196 recv_thread.wait();
197}
\ No newline at end of file
lib/std/std.zig+1
...@@ -14,6 +14,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;...@@ -14,6 +14,7 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
14pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;14pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
15pub const AutoHashMap = hash_map.AutoHashMap;15pub const AutoHashMap = hash_map.AutoHashMap;
16pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;16pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
17pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent;
17pub const BufMap = @import("buf_map.zig").BufMap;18pub const BufMap = @import("buf_map.zig").BufMap;
18pub const BufSet = @import("buf_set.zig").BufSet;19pub const BufSet = @import("buf_set.zig").BufSet;
19pub const ChildProcess = @import("child_process.zig").ChildProcess;20pub const ChildProcess = @import("child_process.zig").ChildProcess;