authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 12:22:55-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 12:22:55-05:00
log8d32d256198589eeaccb92892e5b3145c097514c
tree6c54dd3a62adafa79233777fa6ca7bccd054bc4f
parent7a4ed10981cf7ac5bed93b2c9bad280ad31c50cf
parentbfc1772d8ead9e8fcde0a315411ddcd040cc3d28

Merge branch 'emekoi-windows-mutex'


7 files changed, 242 insertions(+), 61 deletions(-)

CMakeLists.txt+16-15
...@@ -454,10 +454,10 @@ set(ZIG_STD_FILES...@@ -454,10 +454,10 @@ set(ZIG_STD_FILES
454 "crypto/hmac.zig"454 "crypto/hmac.zig"
455 "crypto/index.zig"455 "crypto/index.zig"
456 "crypto/md5.zig"456 "crypto/md5.zig"
457 "crypto/poly1305.zig"
457 "crypto/sha1.zig"458 "crypto/sha1.zig"
458 "crypto/sha2.zig"459 "crypto/sha2.zig"
459 "crypto/sha3.zig"460 "crypto/sha3.zig"
460 "crypto/poly1305.zig"
461 "crypto/x25519.zig"461 "crypto/x25519.zig"
462 "cstr.zig"462 "cstr.zig"
463 "debug/failing_allocator.zig"463 "debug/failing_allocator.zig"
...@@ -566,9 +566,9 @@ set(ZIG_STD_FILES...@@ -566,9 +566,9 @@ set(ZIG_STD_FILES
566 "math/tan.zig"566 "math/tan.zig"
567 "math/tanh.zig"567 "math/tanh.zig"
568 "math/trunc.zig"568 "math/trunc.zig"
569 "mem.zig"
569 "meta/index.zig"570 "meta/index.zig"
570 "meta/trait.zig"571 "meta/trait.zig"
571 "mem.zig"
572 "mutex.zig"572 "mutex.zig"
573 "net.zig"573 "net.zig"
574 "os/child_process.zig"574 "os/child_process.zig"
...@@ -576,16 +576,16 @@ set(ZIG_STD_FILES...@@ -576,16 +576,16 @@ set(ZIG_STD_FILES
576 "os/darwin/errno.zig"576 "os/darwin/errno.zig"
577 "os/epoch.zig"577 "os/epoch.zig"
578 "os/file.zig"578 "os/file.zig"
579 "os/freebsd/errno.zig"
580 "os/freebsd/index.zig"
579 "os/get_app_data_dir.zig"581 "os/get_app_data_dir.zig"
580 "os/get_user_id.zig"582 "os/get_user_id.zig"
581 "os/index.zig"583 "os/index.zig"
584 "os/linux/arm64.zig"
582 "os/linux/errno.zig"585 "os/linux/errno.zig"
583 "os/linux/index.zig"586 "os/linux/index.zig"
584 "os/linux/vdso.zig"587 "os/linux/vdso.zig"
585 "os/linux/x86_64.zig"588 "os/linux/x86_64.zig"
586 "os/linux/arm64.zig"
587 "os/freebsd/errno.zig"
588 "os/freebsd/index.zig"
589 "os/path.zig"589 "os/path.zig"
590 "os/time.zig"590 "os/time.zig"
591 "os/uefi.zig"591 "os/uefi.zig"
...@@ -612,6 +612,16 @@ set(ZIG_STD_FILES...@@ -612,6 +612,16 @@ set(ZIG_STD_FILES
612 "special/compiler_rt/comparetf2.zig"612 "special/compiler_rt/comparetf2.zig"
613 "special/compiler_rt/divti3.zig"613 "special/compiler_rt/divti3.zig"
614 "special/compiler_rt/extendXfYf2.zig"614 "special/compiler_rt/extendXfYf2.zig"
615 "special/compiler_rt/fixdfdi.zig"
616 "special/compiler_rt/fixdfsi.zig"
617 "special/compiler_rt/fixdfti.zig"
618 "special/compiler_rt/fixint.zig"
619 "special/compiler_rt/fixsfdi.zig"
620 "special/compiler_rt/fixsfsi.zig"
621 "special/compiler_rt/fixsfti.zig"
622 "special/compiler_rt/fixtfdi.zig"
623 "special/compiler_rt/fixtfsi.zig"
624 "special/compiler_rt/fixtfti.zig"
615 "special/compiler_rt/fixuint.zig"625 "special/compiler_rt/fixuint.zig"
616 "special/compiler_rt/fixunsdfdi.zig"626 "special/compiler_rt/fixunsdfdi.zig"
617 "special/compiler_rt/fixunsdfsi.zig"627 "special/compiler_rt/fixunsdfsi.zig"
...@@ -622,16 +632,6 @@ set(ZIG_STD_FILES...@@ -622,16 +632,6 @@ set(ZIG_STD_FILES
622 "special/compiler_rt/fixunstfdi.zig"632 "special/compiler_rt/fixunstfdi.zig"
623 "special/compiler_rt/fixunstfsi.zig"633 "special/compiler_rt/fixunstfsi.zig"
624 "special/compiler_rt/fixunstfti.zig"634 "special/compiler_rt/fixunstfti.zig"
625 "special/compiler_rt/fixint.zig"
626 "special/compiler_rt/fixdfdi.zig"
627 "special/compiler_rt/fixdfsi.zig"
628 "special/compiler_rt/fixdfti.zig"
629 "special/compiler_rt/fixsfdi.zig"
630 "special/compiler_rt/fixsfsi.zig"
631 "special/compiler_rt/fixsfti.zig"
632 "special/compiler_rt/fixtfdi.zig"
633 "special/compiler_rt/fixtfsi.zig"
634 "special/compiler_rt/fixtfti.zig"
635 "special/compiler_rt/floattidf.zig"635 "special/compiler_rt/floattidf.zig"
636 "special/compiler_rt/floattisf.zig"636 "special/compiler_rt/floattisf.zig"
637 "special/compiler_rt/floattitf.zig"637 "special/compiler_rt/floattitf.zig"
...@@ -656,6 +656,7 @@ set(ZIG_STD_FILES...@@ -656,6 +656,7 @@ set(ZIG_STD_FILES
656 "special/panic.zig"656 "special/panic.zig"
657 "special/test_runner.zig"657 "special/test_runner.zig"
658 "spinlock.zig"658 "spinlock.zig"
659 "statically_initialized_mutex.zig"
659 "unicode.zig"660 "unicode.zig"
660 "zig/ast.zig"661 "zig/ast.zig"
661 "zig/index.zig"662 "zig/index.zig"
std/fmt/index.zig-2
...@@ -982,13 +982,11 @@ test "fmt.format" {...@@ -982,13 +982,11 @@ test "fmt.format" {
982 context = BufPrintContext{ .remaining = buf1[0..] };982 context = BufPrintContext{ .remaining = buf1[0..] };
983 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite);983 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite);
984 res = buf1[0 .. buf1.len - context.remaining.len];984 res = buf1[0 .. buf1.len - context.remaining.len];
985 debug.warn("{}\n", res);
986 assert(mem.eql(u8, res, "a"));985 assert(mem.eql(u8, res, "a"));
987986
988 context = BufPrintContext{ .remaining = buf1[0..] };987 context = BufPrintContext{ .remaining = buf1[0..] };
989 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite);988 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite);
990 res = buf1[0 .. buf1.len - context.remaining.len];989 res = buf1[0 .. buf1.len - context.remaining.len];
991 debug.warn("{}\n", res);
992 assert(mem.eql(u8, res, "1100"));990 assert(mem.eql(u8, res, "1100"));
993 }991 }
994 {992 {
std/index.zig+2
...@@ -9,6 +9,7 @@ pub const DynLib = @import("dynamic_library.zig").DynLib;...@@ -9,6 +9,7 @@ pub const DynLib = @import("dynamic_library.zig").DynLib;
9pub const HashMap = @import("hash_map.zig").HashMap;9pub const HashMap = @import("hash_map.zig").HashMap;
10pub const LinkedList = @import("linked_list.zig").LinkedList;10pub const LinkedList = @import("linked_list.zig").LinkedList;
11pub const Mutex = @import("mutex.zig").Mutex;11pub const Mutex = @import("mutex.zig").Mutex;
12pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex;
12pub const SegmentedList = @import("segmented_list.zig").SegmentedList;13pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
13pub const SpinLock = @import("spinlock.zig").SpinLock;14pub const SpinLock = @import("spinlock.zig").SpinLock;
1415
...@@ -55,6 +56,7 @@ test "std" {...@@ -55,6 +56,7 @@ test "std" {
55 _ = @import("hash_map.zig");56 _ = @import("hash_map.zig");
56 _ = @import("linked_list.zig");57 _ = @import("linked_list.zig");
57 _ = @import("mutex.zig");58 _ = @import("mutex.zig");
59 _ = @import("statically_initialized_mutex.zig");
58 _ = @import("segmented_list.zig");60 _ = @import("segmented_list.zig");
59 _ = @import("spinlock.zig");61 _ = @import("spinlock.zig");
60 62
std/mutex.zig+71-44
...@@ -5,74 +5,99 @@ const AtomicRmwOp = builtin.AtomicRmwOp;...@@ -5,74 +5,99 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
5const assert = std.debug.assert;5const assert = std.debug.assert;
6const SpinLock = std.SpinLock;6const SpinLock = std.SpinLock;
7const linux = std.os.linux;7const linux = std.os.linux;
8const windows = std.os.windows;
89
9/// Lock may be held only once. If the same thread10/// Lock may be held only once. If the same thread
10/// tries to acquire the same mutex twice, it deadlocks.11/// tries to acquire the same mutex twice, it deadlocks.
12/// This type must be initialized at runtime, and then deinitialized when no
13/// longer needed, to free resources.
14/// If you need static initialization, use std.StaticallyInitializedMutex.
11/// The Linux implementation is based on mutex3 from15/// The Linux implementation is based on mutex3 from
12/// https://www.akkadia.org/drepper/futex.pdf16/// https://www.akkadia.org/drepper/futex.pdf
13pub const Mutex = struct {17pub const Mutex = switch(builtin.os) {
14 /// 0: unlocked18 builtin.Os.linux => struct {
15 /// 1: locked, no waiters19 /// 0: unlocked
16 /// 2: locked, one or more waiters20 /// 1: locked, no waiters
17 linux_lock: @typeOf(linux_lock_init),21 /// 2: locked, one or more waiters
1822 lock: i32,
19 /// TODO better implementation than spin lock23
20 spin_lock: @typeOf(spin_lock_init),24 pub const Held = struct {
2125 mutex: *Mutex,
22 const linux_lock_init = if (builtin.os == builtin.Os.linux) i32(0) else {};26
23 const spin_lock_init = if (builtin.os != builtin.Os.linux) SpinLock.init() else {};27 pub fn release(self: Held) void {
2428 const c = @atomicRmw(i32, &self.mutex.lock, AtomicRmwOp.Sub, 1, AtomicOrder.Release);
25 pub const Held = struct {
26 mutex: *Mutex,
27
28 pub fn release(self: Held) void {
29 if (builtin.os == builtin.Os.linux) {
30 const c = @atomicRmw(i32, &self.mutex.linux_lock, AtomicRmwOp.Sub, 1, AtomicOrder.Release);
31 if (c != 1) {29 if (c != 1) {
32 _ = @atomicRmw(i32, &self.mutex.linux_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.Release);30 _ = @atomicRmw(i32, &self.mutex.lock, AtomicRmwOp.Xchg, 0, AtomicOrder.Release);
33 const rc = linux.futex_wake(&self.mutex.linux_lock, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1);31 const rc = linux.futex_wake(&self.mutex.lock, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1);
34 switch (linux.getErrno(rc)) {32 switch (linux.getErrno(rc)) {
35 0 => {},33 0 => {},
36 linux.EINVAL => unreachable,34 linux.EINVAL => unreachable,
37 else => unreachable,35 else => unreachable,
38 }36 }
39 }37 }
40 } else {
41 SpinLock.Held.release(SpinLock.Held{ .spinlock = &self.mutex.spin_lock });
42 }38 }
39 };
40
41 pub fn init() Mutex {
42 return Mutex {
43 .lock = 0,
44 };
43 }45 }
44 };
4546
46 pub fn init() Mutex {47 pub fn deinit(self: *Mutex) void {}
47 return Mutex{
48 .linux_lock = linux_lock_init,
49 .spin_lock = spin_lock_init,
50 };
51 }
5248
53 pub fn acquire(self: *Mutex) Held {49 pub fn acquire(self: *Mutex) Held {
54 if (builtin.os == builtin.Os.linux) {50 var c = @cmpxchgWeak(i32, &self.lock, 0, 1, AtomicOrder.Acquire, AtomicOrder.Monotonic) orelse
55 var c = @cmpxchgWeak(i32, &self.linux_lock, 0, 1, AtomicOrder.Acquire, AtomicOrder.Monotonic) orelse
56 return Held{ .mutex = self };51 return Held{ .mutex = self };
57 if (c != 2)52 if (c != 2)
58 c = @atomicRmw(i32, &self.linux_lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);53 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
59 while (c != 0) {54 while (c != 0) {
60 const rc = linux.futex_wait(&self.linux_lock, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, 2, null);55 const rc = linux.futex_wait(&self.lock, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, 2, null);
61 switch (linux.getErrno(rc)) {56 switch (linux.getErrno(rc)) {
62 0, linux.EINTR, linux.EAGAIN => {},57 0, linux.EINTR, linux.EAGAIN => {},
63 linux.EINVAL => unreachable,58 linux.EINVAL => unreachable,
64 else => unreachable,59 else => unreachable,
65 }60 }
66 c = @atomicRmw(i32, &self.linux_lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);61 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
67 }62 }
68 } else {63 return Held { .mutex = self };
69 _ = self.spin_lock.acquire();
70 }64 }
71 return Held{ .mutex = self };65 },
72 }66 // TODO once https://github.com/ziglang/zig/issues/287 (copy elision) is solved, we can make a
67 // better implementation of this. The problem is we need the init() function to have access to
68 // the address of the CRITICAL_SECTION, and then have it not move.
69 builtin.Os.windows => std.StaticallyInitializedMutex,
70 else => struct {
71 /// TODO better implementation than spin lock.
72 /// When changing this, one must also change the corresponding
73 /// std.StaticallyInitializedMutex code, since it aliases this type,
74 /// under the assumption that it works both statically and at runtime.
75 lock: SpinLock,
76
77 pub const Held = struct {
78 mutex: *Mutex,
79
80 pub fn release(self: Held) void {
81 SpinLock.Held.release(SpinLock.Held { .spinlock = &self.mutex.lock });
82 }
83 };
84
85 pub fn init() Mutex {
86 return Mutex {
87 .lock = SpinLock.init(),
88 };
89 }
90
91 pub fn deinit(self: *Mutex) void {}
92
93 pub fn acquire(self: *Mutex) Held {
94 _ = self.lock.acquire();
95 return Held { .mutex = self };
96 }
97 },
73};98};
7499
75const Context = struct {100const TestContext = struct {
76 mutex: *Mutex,101 mutex: *Mutex,
77 data: i128,102 data: i128,
78103
...@@ -90,7 +115,9 @@ test "std.Mutex" {...@@ -90,7 +115,9 @@ test "std.Mutex" {
90 var a = &fixed_buffer_allocator.allocator;115 var a = &fixed_buffer_allocator.allocator;
91116
92 var mutex = Mutex.init();117 var mutex = Mutex.init();
93 var context = Context{118 defer mutex.deinit();
119
120 var context = TestContext{
94 .mutex = &mutex,121 .mutex = &mutex,
95 .data = 0,122 .data = 0,
96 };123 };
...@@ -103,12 +130,12 @@ test "std.Mutex" {...@@ -103,12 +130,12 @@ test "std.Mutex" {
103 for (threads) |t|130 for (threads) |t|
104 t.wait();131 t.wait();
105132
106 std.debug.assertOrPanic(context.data == thread_count * Context.incr_count);133 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
107}134}
108135
109fn worker(ctx: *Context) void {136fn worker(ctx: *TestContext) void {
110 var i: usize = 0;137 var i: usize = 0;
111 while (i != Context.incr_count) : (i += 1) {138 while (i != TestContext.incr_count) : (i += 1) {
112 const held = ctx.mutex.acquire();139 const held = ctx.mutex.acquire();
113 defer held.release();140 defer held.release();
114141
std/os/windows/index.zig+1
...@@ -49,6 +49,7 @@ pub const UNICODE = false;...@@ -49,6 +49,7 @@ pub const UNICODE = false;
49pub const WCHAR = u16;49pub const WCHAR = u16;
50pub const WORD = u16;50pub const WORD = u16;
51pub const LARGE_INTEGER = i64;51pub const LARGE_INTEGER = i64;
52pub const LONG = c_long;
5253
53pub const TRUE = 1;54pub const TRUE = 1;
54pub const FALSE = 0;55pub const FALSE = 0;
std/os/windows/kernel32.zig+47
...@@ -220,3 +220,50 @@ pub const FOREGROUND_BLUE = 1;...@@ -220,3 +220,50 @@ pub const FOREGROUND_BLUE = 1;
220pub const FOREGROUND_GREEN = 2;220pub const FOREGROUND_GREEN = 2;
221pub const FOREGROUND_RED = 4;221pub const FOREGROUND_RED = 4;
222pub const FOREGROUND_INTENSITY = 8;222pub const FOREGROUND_INTENSITY = 8;
223
224pub extern "kernel32" stdcallcc fn InitializeCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
225pub extern "kernel32" stdcallcc fn EnterCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
226pub extern "kernel32" stdcallcc fn LeaveCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
227pub extern "kernel32" stdcallcc fn DeleteCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
228
229pub const LIST_ENTRY = extern struct {
230 Flink: *LIST_ENTRY,
231 Blink: *LIST_ENTRY,
232};
233
234pub const RTL_CRITICAL_SECTION_DEBUG = extern struct {
235 Type: WORD,
236 CreatorBackTraceIndex: WORD,
237 CriticalSection: *RTL_CRITICAL_SECTION,
238 ProcessLocksList: LIST_ENTRY,
239 EntryCount: DWORD,
240 ContentionCount: DWORD,
241 Flags: DWORD,
242 CreatorBackTraceIndexHigh: WORD,
243 SpareWORD: WORD,
244};
245
246pub const RTL_CRITICAL_SECTION = extern struct {
247 DebugInfo: *RTL_CRITICAL_SECTION_DEBUG,
248 LockCount: LONG,
249 RecursionCount: LONG,
250 OwningThread: HANDLE,
251 LockSemaphore: HANDLE,
252 SpinCount: ULONG_PTR,
253};
254
255pub const CRITICAL_SECTION = RTL_CRITICAL_SECTION;
256pub const INIT_ONCE = RTL_RUN_ONCE;
257pub const INIT_ONCE_STATIC_INIT = RTL_RUN_ONCE_INIT;
258
259pub extern "kernel32" stdcallcc fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter: ?*c_void, Context: ?*c_void) BOOL;
260
261pub const INIT_ONCE_FN = extern fn(InitOnce: *INIT_ONCE, Parameter: ?*c_void, Context: ?*c_void) BOOL;
262
263pub const RTL_RUN_ONCE = extern struct {
264 Ptr: ?*c_void,
265};
266
267pub const RTL_RUN_ONCE_INIT = RTL_RUN_ONCE {
268 .Ptr = null,
269};
std/statically_initialized_mutex.zig created+105
...@@ -0,0 +1,105 @@
1const std = @import("index.zig");
2const builtin = @import("builtin");
3const AtomicOrder = builtin.AtomicOrder;
4const AtomicRmwOp = builtin.AtomicRmwOp;
5const assert = std.debug.assert;
6const windows = std.os.windows;
7
8/// Lock may be held only once. If the same thread
9/// tries to acquire the same mutex twice, it deadlocks.
10/// This type is intended to be initialized statically. If you don't
11/// require static initialization, use std.Mutex.
12/// On Windows, this mutex allocates resources when it is
13/// first used, and the resources cannot be freed.
14/// On Linux, this is an alias of std.Mutex.
15pub const StaticallyInitializedMutex = switch(builtin.os) {
16 builtin.Os.linux => std.Mutex,
17 builtin.Os.windows => struct {
18 lock: windows.CRITICAL_SECTION,
19 init_once: windows.RTL_RUN_ONCE,
20
21 pub const Held = struct {
22 mutex: *StaticallyInitializedMutex,
23
24 pub fn release(self: Held) void {
25 windows.LeaveCriticalSection(&self.mutex.lock);
26 }
27 };
28
29 pub fn init() StaticallyInitializedMutex {
30 return StaticallyInitializedMutex {
31 .lock = undefined,
32 .init_once = windows.INIT_ONCE_STATIC_INIT,
33 };
34 }
35
36 extern fn initCriticalSection(
37 InitOnce: *windows.RTL_RUN_ONCE,
38 Parameter: ?*c_void,
39 Context: ?*c_void,
40 ) windows.BOOL {
41 const lock = @ptrCast(*windows.CRITICAL_SECTION, @alignCast(@alignOf(windows.CRITICAL_SECTION), Parameter));
42 windows.InitializeCriticalSection(lock);
43 return windows.TRUE;
44 }
45
46 /// TODO: once https://github.com/ziglang/zig/issues/287 is solved and std.Mutex has a better
47 /// implementation of a runtime initialized mutex, remove this function.
48 pub fn deinit(self: *StaticallyInitializedMutex) void {
49 assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
50 windows.DeleteCriticalSection(&self.lock);
51 }
52
53 pub fn acquire(self: *StaticallyInitializedMutex) Held {
54 assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
55 windows.EnterCriticalSection(&self.lock);
56 return Held { .mutex = self };
57 }
58 },
59 else => std.Mutex,
60};
61
62test "std.StaticallyInitializedMutex" {
63 const TestContext = struct {
64 data: i128,
65
66 const TestContext = @This();
67 const incr_count = 10000;
68
69 var mutex = StaticallyInitializedMutex.init();
70
71 fn worker(ctx: *TestContext) void {
72 var i: usize = 0;
73 while (i != TestContext.incr_count) : (i += 1) {
74 const held = mutex.acquire();
75 defer held.release();
76
77 ctx.data += 1;
78 }
79 }
80 };
81
82 var direct_allocator = std.heap.DirectAllocator.init();
83 defer direct_allocator.deinit();
84
85 var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024);
86 defer direct_allocator.allocator.free(plenty_of_memory);
87
88 var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
89 var a = &fixed_buffer_allocator.allocator;
90
91
92 var context = TestContext{
93 .data = 0,
94 };
95
96 const thread_count = 10;
97 var threads: [thread_count]*std.os.Thread = undefined;
98 for (threads) |*t| {
99 t.* = try std.os.spawnThread(&context, TestContext.worker);
100 }
101 for (threads) |t|
102 t.wait();
103
104 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
105}