Commit 4e621d4260

Andrew Kelley <andrew@ziglang.org>
2020-12-19 23:03:03
workaround for std lib AutoResetEvent bug
1 parent e00b6db
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
-    // - <std.ResetEvent pointer>: 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
+    /// - <std.ResetEvent pointer>: 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 {
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,
+    }
+}
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();
 
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();
+    }
 }