const std = @import("std.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); } }