zig/std/statically_initialized_mutex.zig
Andrew Kelley c2db077574
std.debug.assert: remove special case for test builds
Previously, std.debug.assert would `@panic` in test builds,
if the assertion failed. Now, it's always `unreachable`.

This makes release mode test builds more accurately test
the actual code that will be run.

However this requires tests to call `std.testing.expect`
rather than `std.debug.assert` to make sure output is correct.

Here is the explanation of when to use either one, copied from
the assert doc comments:

Inside a test block, it is best to use the `std.testing` module
rather than assert, because assert may not detect a test failure
in ReleaseFast and ReleaseSafe mode. Outside of a test block, assert
is the correct function to use.

closes #1304
2019-02-08 18:23:38 -05:00

112 lines
3.8 KiB
Zig

const std = @import("index.zig");
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
const AtomicRmwOp = builtin.AtomicRmwOp;
const assert = std.debug.assert;
const expect = std.testing.expect;
const windows = std.os.windows;
/// Lock may be held only once. If the same thread
/// tries to acquire the same mutex twice, it deadlocks.
/// This type is intended to be initialized statically. If you don't
/// require static initialization, use std.Mutex.
/// On Windows, this mutex allocates resources when it is
/// first used, and the resources cannot be freed.
/// On Linux, this is an alias of std.Mutex.
pub const StaticallyInitializedMutex = switch(builtin.os) {
builtin.Os.linux => std.Mutex,
builtin.Os.windows => struct {
lock: windows.CRITICAL_SECTION,
init_once: windows.RTL_RUN_ONCE,
pub const Held = struct {
mutex: *StaticallyInitializedMutex,
pub fn release(self: Held) void {
windows.LeaveCriticalSection(&self.mutex.lock);
}
};
pub fn init() StaticallyInitializedMutex {
return StaticallyInitializedMutex {
.lock = undefined,
.init_once = windows.INIT_ONCE_STATIC_INIT,
};
}
extern fn initCriticalSection(
InitOnce: *windows.RTL_RUN_ONCE,
Parameter: ?*c_void,
Context: ?*c_void,
) windows.BOOL {
const lock = @ptrCast(*windows.CRITICAL_SECTION, @alignCast(@alignOf(windows.CRITICAL_SECTION), Parameter));
windows.InitializeCriticalSection(lock);
return windows.TRUE;
}
/// TODO: once https://github.com/ziglang/zig/issues/287 is solved and std.Mutex has a better
/// implementation of a runtime initialized mutex, remove this function.
pub fn deinit(self: *StaticallyInitializedMutex) void {
assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
windows.DeleteCriticalSection(&self.lock);
}
pub fn acquire(self: *StaticallyInitializedMutex) Held {
assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
windows.EnterCriticalSection(&self.lock);
return Held { .mutex = self };
}
},
else => std.Mutex,
};
test "std.StaticallyInitializedMutex" {
const TestContext = struct {
data: i128,
const TestContext = @This();
const incr_count = 10000;
var mutex = StaticallyInitializedMutex.init();
fn worker(ctx: *TestContext) void {
var i: usize = 0;
while (i != TestContext.incr_count) : (i += 1) {
const held = mutex.acquire();
defer held.release();
ctx.data += 1;
}
}
};
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024);
defer direct_allocator.allocator.free(plenty_of_memory);
var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
var a = &fixed_buffer_allocator.allocator;
var context = TestContext{
.data = 0,
};
if (builtin.single_threaded) {
TestContext.worker(&context);
expect(context.data == TestContext.incr_count);
} else {
const thread_count = 10;
var threads: [thread_count]*std.os.Thread = undefined;
for (threads) |*t| {
t.* = try std.os.spawnThread(&context, TestContext.worker);
}
for (threads) |t|
t.wait();
expect(context.data == thread_count * TestContext.incr_count);
}
}