authorgravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-28 11:27:23-05:00
committergravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-30 21:49:00-05:00
logf0fa129e9b1cdbd90b231da14c6cd99c9413aa98
tree4e54d26a4ec212aefc91d36ec3a9391c881973fe
parent7b323f84ca876c86bbe06f132d5a5d3775def3a2

std.Thread: more cleanup & testing


5 files changed, 184 insertions(+), 146 deletions(-)

doc/langref.html.in+6-6
......@@ -958,14 +958,14 @@ const assert = std.debug.assert;
958958threadlocal var x: i32 = 1234;
959959
960960test "thread local storage" {
961 const thread1 = try std.Thread.spawn(testTls, {});
962 const thread2 = try std.Thread.spawn(testTls, {});
963 testTls({});
964 thread1.wait();
965 thread2.wait();
961 const thread1 = try std.Thread.spawn(.{}, testTls, .{});
962 const thread2 = try std.Thread.spawn(.{}, testTls, .{});
963 testTls();
964 thread1.join();
965 thread2.join();
966966}
967967
968fn testTls(_: void) void {
968fn testTls() void {
969969 assert(x == 1234);
970970 x += 1;
971971 assert(x == 1235);
lib/std/Thread.zig+173-127
......@@ -24,17 +24,6 @@ pub const Condition = @import("Thread/Condition.zig");
2424
2525pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
2626
27test "std.Thread" {
28 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
29 _ = AutoResetEvent;
30 _ = Futex;
31 _ = ResetEvent;
32 _ = StaticResetEvent;
33 _ = Mutex;
34 _ = Semaphore;
35 _ = Condition;
36}
37
3827pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;
3928
4029const Thread = @This();
......@@ -50,7 +39,6 @@ else
5039impl: Impl,
5140
5241/// Represents a unique ID per thread.
53/// May be an integer or pointer depending on the platform.
5442pub const Id = u64;
5543
5644/// Returns the platform ID of the callers thread.
......@@ -79,7 +67,7 @@ pub const SpawnConfig = struct {
7967 stack_size: usize = 16 * 1024 * 1024,
8068};
8169
82pub const SpawnError = error {
70pub const SpawnError = error{
8371 /// A system-imposed limit on the number of threads was encountered.
8472 /// There are a number of limits that may trigger this error:
8573 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
......@@ -115,7 +103,7 @@ pub const SpawnError = error {
115103/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.
116104pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
117105 if (std.builtin.single_threaded) {
118 @compileError("cannot spawn thread when building in single-threaded mode");
106 @compileError("Cannot spawn thread when building in single-threaded mode");
119107 }
120108
121109 const impl = try Impl.spawn(config, function, args);
......@@ -132,11 +120,13 @@ pub fn getHandle(self: Thread) Handle {
132120}
133121
134122/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
123/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
135124pub fn detach(self: Thread) void {
136125 return self.impl.detach();
137126}
138127
139128/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
129/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
140130pub fn join(self: Thread) void {
141131 return self.impl.join();
142132}
......@@ -200,6 +190,8 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
200190 }
201191}
202192
193/// We can't compile error in the `Impl` switch statement as its eagerly evaluated.
194/// So instead, we compile-error on the methods themselves for platforms which don't support threads.
203195const UnsupportedImpl = struct {
204196 pub const ThreadHandle = void;
205197
......@@ -212,7 +204,7 @@ const UnsupportedImpl = struct {
212204 }
213205
214206 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
215 return unsupported(.{config, f, args});
207 return unsupported(.{ config, f, args });
216208 }
217209
218210 fn getHandle(self: Impl) ThreadHandle {
......@@ -225,7 +217,7 @@ const UnsupportedImpl = struct {
225217
226218 fn join(self: Impl) void {
227219 return unsupported(self);
228 }
220 }
229221
230222 fn unsupported(unusued: anytype) noreturn {
231223 @compileLog("Unsupported operating system", target.os.tag);
......@@ -244,6 +236,7 @@ const WindowsThreadImpl = struct {
244236 }
245237
246238 fn getCpuCount() !usize {
239 // Faster than calling into GetSystemInfo(), even if amortized.
247240 return windows.peb().NumberOfProcessors;
248241 }
249242
......@@ -299,16 +292,17 @@ const WindowsThreadImpl = struct {
299292 // Its also fine if the limit here is incorrect as stack size is only a hint.
300293 var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32);
301294 stack_size = std.math.max(64 * 1024, stack_size);
302
295
303296 instance.thread.thread_handle = windows.kernel32.CreateThread(
304 null,
305 stack_size,
306 Instance.entryFn,
307 @ptrCast(*c_void, instance),
308 0,
297 null,
298 stack_size,
299 Instance.entryFn,
300 @ptrCast(*c_void, instance),
301 0,
309302 null,
310303 ) orelse {
311 return windows.unexpectedError(windows.kernel32.GetLastError());
304 const errno = windows.kernel32.GetLastError();
305 return windows.unexpectedError(errno);
312306 };
313307
314308 return Impl{ .thread = &instance.thread };
......@@ -332,7 +326,7 @@ const WindowsThreadImpl = struct {
332326 windows.CloseHandle(self.thread.thread_handle);
333327 assert(self.thread.completion.load(.SeqCst) == .completed);
334328 self.thread.free();
335 }
329 }
336330};
337331
338332const PosixThreadImpl = struct {
......@@ -374,7 +368,9 @@ const PosixThreadImpl = struct {
374368
375369 fn getCpuCount() !usize {
376370 switch (target.os.tag) {
377 .linux => return LinuxThreadImpl.getCpuCount(),
371 .linux => {
372 return LinuxThreadImpl.getCpuCount();
373 },
378374 .openbsd => {
379375 var count: c_int = undefined;
380376 var count_size: usize = @sizeOf(c_int);
......@@ -413,6 +409,7 @@ const PosixThreadImpl = struct {
413409
414410 const Instance = struct {
415411 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {
412 // @alignCast() below doesn't support zero-sized-types (ZST)
416413 if (@sizeOf(Args) < 1) {
417414 return callFn(f, @as(Args, undefined));
418415 }
......@@ -457,8 +454,9 @@ const PosixThreadImpl = struct {
457454
458455 fn detach(self: Impl) void {
459456 switch (c.pthread_detach(self.handle)) {
460 os.EINVAL => unreachable,
461 os.ESRCH => unreachable,
457 0 => {},
458 os.EINVAL => unreachable, // thread handle is not joinable
459 os.ESRCH => unreachable, // thread handle is invalid
462460 else => unreachable,
463461 }
464462 }
......@@ -466,9 +464,9 @@ const PosixThreadImpl = struct {
466464 fn join(self: Impl) void {
467465 switch (c.pthread_join(self.handle, null)) {
468466 0 => {},
469 os.EINVAL => unreachable,
470 os.ESRCH => unreachable,
471 os.EDEADLK => unreachable,
467 os.EINVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
468 os.ESRCH => unreachable, // thread handle is invalid
469 os.EDEADLK => unreachable, // two threads tried to join each other
472470 else => unreachable,
473471 }
474472 }
......@@ -476,7 +474,7 @@ const PosixThreadImpl = struct {
476474
477475const LinuxThreadImpl = struct {
478476 const linux = os.linux;
479
477
480478 pub const ThreadHandle = i32;
481479
482480 threadlocal var tls_thread_id: ?Id = null;
......@@ -491,7 +489,8 @@ const LinuxThreadImpl = struct {
491489
492490 fn getCpuCount() !usize {
493491 const cpu_set = try os.sched_getaffinity(0);
494 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
492 // TODO: should not need this usize cast
493 return @as(usize, os.CPU_COUNT(cpu_set));
495494 }
496495
497496 thread: *ThreadCompletion,
......@@ -547,7 +546,7 @@ const LinuxThreadImpl = struct {
547546 bytes = std.mem.alignForward(bytes, std.mem.page_size);
548547 break :blk bytes;
549548 };
550
549
551550 // map all memory needed without read/write permissions
552551 // to avoid committing the whole region right away
553552 const mapped = os.mmap(
......@@ -654,7 +653,7 @@ const LinuxThreadImpl = struct {
654653
655654 switch (linux.getErrno(linux.futex_wait(
656655 &self.thread.child_tid.value,
657 linux.FUTEX_WAIT,
656 linux.FUTEX_WAIT,
658657 tid,
659658 null,
660659 ))) {
......@@ -671,98 +670,145 @@ const LinuxThreadImpl = struct {
671670 extern fn __unmap_and_exit(ptr: usize, len: usize) callconv(.C) noreturn;
672671 comptime {
673672 if (target.os.tag == .linux) {
674 asm(switch (target.cpu.arch) {
675 .i386 => (
676 \\.text
677 \\.global __unmap_and_exit
678 \\.type __unmap_and_exit, @function
679 \\__unmap_and_exit:
680 \\ movl $91, %eax
681 \\ movl 4(%esp), %ebx
682 \\ movl 8(%esp), %ecx
683 \\ int $128
684 \\ xorl %ebx, %ebx
685 \\ movl $1, %eax
686 \\ int $128
687 ),
688 .x86_64 => (
689 \\.text
690 \\.global __unmap_and_exit
691 \\.type __unmap_and_exit, @function
692 \\__unmap_and_exit:
693 \\ movl $11, %eax
694 \\ syscall
695 \\ xor %rdi, %rdi
696 \\ movl $60, %eax
697 \\ syscall
698 ),
699 .arm, .armeb, .thumb, .thumbeb => (
700 \\.syntax unified
701 \\.text
702 \\.global __unmap_and_exit
703 \\.type __unmap_and_exit, %function
704 \\__unmap_and_exit:
705 \\ mov r7, #91
706 \\ svc 0
707 \\ mov r7, #1
708 \\ svc 0
709 ),
710 .aarch64, .aarch64_be, .aarch64_32 => (
711 \\.global __unmap_and_exit
712 \\.type __unmap_and_exit, %function
713 \\__unmap_and_exit:
714 \\ mov x8, #215
715 \\ svc 0
716 \\ mov x8, #93
717 \\ svc 0
718 ),
719 .mips, .mipsel, => (
720 \\.set noreorder
721 \\.global __unmap_and_exit
722 \\.type __unmap_and_exit,@function
723 \\__unmap_and_exit:
724 \\ move $sp, $25
725 \\ li $2, 4091
726 \\ syscall
727 \\ li $4, 0
728 \\ li $2, 4001
729 \\ syscall
730 ),
731 .mips64, .mips64el => (
732 \\.set noreorder
733 \\.global __unmap_and_exit
734 \\.type __unmap_and_exit, @function
735 \\__unmap_and_exit:
736 \\ li $2, 4091
737 \\ syscall
738 \\ li $4, 0
739 \\ li $2, 4001
740 \\ syscall
741 ),
742 .powerpc, .powerpc64, .powerpc64le => (
743 \\.text
744 \\.global __unmap_and_exit
745 \\.type __unmap_and_exit, %function
746 \\__unmap_and_exit:
747 \\ li 0, 91
748 \\ sc
749 \\ li 0, 1
750 \\ sc
751 \\ blr
752 ),
753 .riscv64 => (
754 \\.global __unmap_and_exit
755 \\.type __unmap_and_exit, %function
756 \\__unmap_and_exit:
757 \\ li a7, 215
758 \\ ecall
759 \\ li a7, 93
760 \\ ecall
761 ),
762 else => |cpu_arch| {
763 @compileLog("linux arch", cpu_arch, "is not supported");
764 },
765 });
673 asm (switch (target.cpu.arch) {
674 .i386 => (
675 \\.text
676 \\.global __unmap_and_exit
677 \\.type __unmap_and_exit, @function
678 \\__unmap_and_exit:
679 \\ movl $91, %eax
680 \\ movl 4(%esp), %ebx
681 \\ movl 8(%esp), %ecx
682 \\ int $128
683 \\ xorl %ebx, %ebx
684 \\ movl $1, %eax
685 \\ int $128
686 ),
687 .x86_64 => (
688 \\.text
689 \\.global __unmap_and_exit
690 \\.type __unmap_and_exit, @function
691 \\__unmap_and_exit:
692 \\ movl $11, %eax
693 \\ syscall
694 \\ xor %rdi, %rdi
695 \\ movl $60, %eax
696 \\ syscall
697 ),
698 .arm, .armeb, .thumb, .thumbeb => (
699 \\.syntax unified
700 \\.text
701 \\.global __unmap_and_exit
702 \\.type __unmap_and_exit, %function
703 \\__unmap_and_exit:
704 \\ mov r7, #91
705 \\ svc 0
706 \\ mov r7, #1
707 \\ svc 0
708 ),
709 .aarch64, .aarch64_be, .aarch64_32 => (
710 \\.global __unmap_and_exit
711 \\.type __unmap_and_exit, %function
712 \\__unmap_and_exit:
713 \\ mov x8, #215
714 \\ svc 0
715 \\ mov x8, #93
716 \\ svc 0
717 ),
718 .mips,
719 .mipsel,
720 => (
721 \\.set noreorder
722 \\.global __unmap_and_exit
723 \\.type __unmap_and_exit,@function
724 \\__unmap_and_exit:
725 \\ move $sp, $25
726 \\ li $2, 4091
727 \\ syscall
728 \\ li $4, 0
729 \\ li $2, 4001
730 \\ syscall
731 ),
732 .mips64, .mips64el => (
733 \\.set noreorder
734 \\.global __unmap_and_exit
735 \\.type __unmap_and_exit, @function
736 \\__unmap_and_exit:
737 \\ li $2, 4091
738 \\ syscall
739 \\ li $4, 0
740 \\ li $2, 4001
741 \\ syscall
742 ),
743 .powerpc, .powerpc64, .powerpc64le => (
744 \\.text
745 \\.global __unmap_and_exit
746 \\.type __unmap_and_exit, %function
747 \\__unmap_and_exit:
748 \\ li 0, 91
749 \\ sc
750 \\ li 0, 1
751 \\ sc
752 \\ blr
753 ),
754 .riscv64 => (
755 \\.global __unmap_and_exit
756 \\.type __unmap_and_exit, %function
757 \\__unmap_and_exit:
758 \\ li a7, 215
759 \\ ecall
760 \\ li a7, 93
761 \\ ecall
762 ),
763 else => |cpu_arch| {
764 @compileLog("linux arch", cpu_arch, "is not supported");
765 },
766 });
766767 }
767768 }
768};
\ No newline at end of file
769};
770
771test "std.Thread" {
772 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
773 _ = AutoResetEvent;
774 _ = Futex;
775 _ = ResetEvent;
776 _ = StaticResetEvent;
777 _ = Mutex;
778 _ = Semaphore;
779 _ = Condition;
780}
781
782fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
783 value.* += 1;
784 event.set();
785}
786
787test "Thread.join" {
788 if (std.builtin.single_threaded) return error.SkipZigTest;
789
790 var value: usize = 0;
791 var event: ResetEvent = undefined;
792 try event.init();
793 defer event.deinit();
794
795 const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event});
796 thread.join();
797
798 try std.testing.expectEqual(value, 1);
799}
800
801test "Thread.detach" {
802 if (std.builtin.single_threaded) return error.SkipZigTest;
803
804 var value: usize = 0;
805 var event: ResetEvent = undefined;
806 try event.init();
807 defer event.deinit();
808
809 const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event});
810 thread.detach();
811
812 event.wait();
813 try std.testing.expectEqual(value, 1);
814}
\ No newline at end of file
lib/std/Thread/Futex.zig+3-3
......@@ -407,7 +407,7 @@ test "Futex - wait/wake" {
407407
408408test "Futex - Signal" {
409409 if (single_threaded) {
410 return;
410 return error.SkipZigTest;
411411 }
412412
413413 const Paddle = struct {
......@@ -449,7 +449,7 @@ test "Futex - Signal" {
449449
450450test "Futex - Broadcast" {
451451 if (single_threaded) {
452 return;
452 return error.SkipZigTest;
453453 }
454454
455455 const Context = struct {
......@@ -506,7 +506,7 @@ test "Futex - Broadcast" {
506506
507507test "Futex - Chain" {
508508 if (single_threaded) {
509 return;
509 return error.SkipZigTest;
510510 }
511511
512512 const Signal = struct {
lib/std/c.zig+1
......@@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us
277277pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;
278278pub extern "c" fn pthread_self() pthread_t;
279279pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;
280pub extern "c" fn pthread_detach(thread: pthread_t) c_int;
280281pub extern "c" fn pthread_atfork(
281282 prepare: ?fn () callconv(.C) void,
282283 parent: ?fn () callconv(.C) void,
lib/std/os/test.zig+1-10
......@@ -321,17 +321,8 @@ test "std.Thread.getCurrentId" {
321321
322322 var thread_current_id: Thread.Id = undefined;
323323 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
324 const thread_id = thread.getHandle();
325324 thread.join();
326 if (Thread.use_pthreads) {
327 try expect(thread_current_id == thread_id);
328 } else if (native_os == .windows) {
329 try expect(Thread.getCurrentId() != thread_current_id);
330 } else {
331 // If the thread completes very quickly, then thread_id can be 0. See the
332 // documentation comments for `std.Thread.handle`.
333 try expect(thread_id == 0 or thread_current_id == thread_id);
334 }
325 try expect(Thread.getCurrentId() != thread_current_id);
335326}
336327
337328test "spawn threads" {