diff --git a/lib/std/auto_reset_event.zig b/lib/std/auto_reset_event.zig index 7e13dc1aba..3c7e65e362 100644 --- a/lib/std/auto_reset_event.zig +++ b/lib/std/auto_reset_event.zig @@ -11,33 +11,33 @@ const assert = std.debug.assert; /// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`. /// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like). pub const AutoResetEvent = struct { - // AutoResetEvent has 3 possible states: - // - UNSET: the AutoResetEvent is currently unset - // - SET: the AutoResetEvent was notified before a wait() was called - // - : there is an active waiter waiting for a notification. - // - // When attempting to wait: - // if the event is unset, it registers a ResetEvent pointer to be notified when the event is set - // if the event is already set, then it consumes the notification and resets the event. - // - // When attempting to notify: - // if the event is unset, then we set the event - // if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent - // - // This ensures that the event is automatically reset after a wait() has been issued - // and avoids the race condition when using std.ResetEvent in the following scenario: - // thread 1 | thread 2 - // std.ResetEvent.wait() | - // | std.ResetEvent.set() - // | std.ResetEvent.set() - // std.ResetEvent.reset() | - // std.ResetEvent.wait() | (missed the second .set() notification above) + /// AutoResetEvent has 3 possible states: + /// - UNSET: the AutoResetEvent is currently unset + /// - SET: the AutoResetEvent was notified before a wait() was called + /// - : there is an active waiter waiting for a notification. + /// + /// When attempting to wait: + /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set + /// if the event is already set, then it consumes the notification and resets the event. + /// + /// When attempting to notify: + /// if the event is unset, then we set the event + /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent + /// + /// This ensures that the event is automatically reset after a wait() has been issued + /// and avoids the race condition when using std.ResetEvent in the following scenario: + /// thread 1 | thread 2 + /// std.ResetEvent.wait() | + /// | std.ResetEvent.set() + /// | std.ResetEvent.set() + /// std.ResetEvent.reset() | + /// std.ResetEvent.wait() | (missed the second .set() notification above) state: usize = UNSET, const UNSET = 0; const SET = 1; - // the minimum alignment for the `*std.ResetEvent` created by wait*() + /// the minimum alignment for the `*std.ResetEvent` created by wait*() const event_align = std.math.max(@alignOf(std.ResetEvent), 2); pub fn wait(self: *AutoResetEvent) void { diff --git a/src/Event.zig b/src/Event.zig new file mode 100644 index 0000000000..2b8d7be998 --- /dev/null +++ b/src/Event.zig @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("std"); +const Event = @This(); + +lock: std.Mutex = .{}, +event: std.ResetEvent = undefined, +state: enum { empty, waiting, notified } = .empty, + +pub fn wait(self: *Event) void { + const held = self.lock.acquire(); + + switch (self.state) { + .empty => { + self.state = .waiting; + self.event = @TypeOf(self.event).init(); + held.release(); + self.event.wait(); + self.event.deinit(); + }, + .waiting => unreachable, + .notified => held.release(), + } +} + +pub fn set(self: *Event) void { + const held = self.lock.acquire(); + + switch (self.state) { + .empty => { + self.state = .notified; + held.release(); + }, + .waiting => { + held.release(); + self.event.set(); + }, + .notified => unreachable, + } +} diff --git a/src/ThreadPool.zig b/src/ThreadPool.zig index 6a59b684be..7d6af3d24c 100644 --- a/src/ThreadPool.zig +++ b/src/ThreadPool.zig @@ -1,3 +1,8 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. const std = @import("std"); const ThreadPool = @This(); diff --git a/src/WaitGroup.zig b/src/WaitGroup.zig index c33d084c28..f17ab580d3 100644 --- a/src/WaitGroup.zig +++ b/src/WaitGroup.zig @@ -1,9 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. const std = @import("std"); const WaitGroup = @This(); +const Event = @import("Event.zig"); lock: std.Mutex = .{}, counter: usize = 0, -event: std.AutoResetEvent = .{}, +event: Event = .{}, pub fn start(self: *WaitGroup) void { const held = self.lock.acquire(); @@ -22,13 +28,15 @@ pub fn stop(self: *WaitGroup) void { } pub fn wait(self: *WaitGroup) void { - { - const held = self.lock.acquire(); - defer held.release(); + while (true) { + { + const held = self.lock.acquire(); + defer held.release(); - if (self.counter == 0) - return; + if (self.counter == 0) + return; + } + + self.event.wait(); } - - self.event.wait(); }