workaround for std lib AutoResetEvent bug

This commit is contained in:
Andrew Kelley 2020-12-19 15:03:03 -07:00
parent e00b6db2aa
commit 4e621d4260
4 changed files with 86 additions and 30 deletions

View File

@ -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 {

43
src/Event.zig Normal file
View File

@ -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,
}
}

View File

@ -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();

View File

@ -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();
}