| author | |
| committer | |
| log | b7da1b2d45bc42a56eea3a143e4237a0712c4769 |
| tree | 5474938657d5dfd9273562c160ad5f1e3a02b824 |
| parent | 5d0dad9acdac854d68e1447b90fd3dbde9ff0b2d |
| parent | c8f90a7e7e10be62634454bf124bef3c6130a0db |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
std.Thread enhancements19 files changed, 898 insertions(+), 682 deletions(-)
doc/langref.html.in+6-6| ... | @@ -958,14 +958,14 @@ const assert = std.debug.assert; | ... | @@ -958,14 +958,14 @@ const assert = std.debug.assert; |
| 958 | threadlocal var x: i32 = 1234; | 958 | threadlocal var x: i32 = 1234; |
| 959 | 959 | ||
| 960 | test "thread local storage" { | 960 | test "thread local storage" { |
| 961 | const thread1 = try std.Thread.spawn(testTls, {}); | 961 | const thread1 = try std.Thread.spawn(.{}, testTls, .{}); |
| 962 | const thread2 = try std.Thread.spawn(testTls, {}); | 962 | const thread2 = try std.Thread.spawn(.{}, testTls, .{}); |
| 963 | testTls({}); | 963 | testTls(); |
| 964 | thread1.wait(); | 964 | thread1.join(); |
| 965 | thread2.wait(); | 965 | thread2.join(); |
| 966 | } | 966 | } |
| 967 | 967 | ||
| 968 | fn testTls(_: void) void { | 968 | fn testTls() void { |
| 969 | assert(x == 1234); | 969 | assert(x == 1234); |
| 970 | x += 1; | 970 | x += 1; |
| 971 | assert(x == 1235); | 971 | assert(x == 1235); |
lib/std/Thread.zig+662-444| ... | @@ -8,7 +8,11 @@ | ... | @@ -8,7 +8,11 @@ |
| 8 | //! primitives that operate on kernel threads. For concurrency primitives that support | 8 | //! primitives that operate on kernel threads. For concurrency primitives that support |
| 9 | //! both evented I/O and async I/O, see the respective names in the top level std namespace. | 9 | //! both evented I/O and async I/O, see the respective names in the top level std namespace. |
| 10 | 10 | ||
| 11 | data: Data, | 11 | const std = @import("std.zig"); |
| 12 | const os = std.os; | ||
| 13 | const assert = std.debug.assert; | ||
| 14 | const target = std.Target.current; | ||
| 15 | const Atomic = std.atomic.Atomic; | ||
| 12 | 16 | ||
| 13 | pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig"); | 17 | pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig"); |
| 14 | pub const Futex = @import("Thread/Futex.zig"); | 18 | pub const Futex = @import("Thread/Futex.zig"); |
| ... | @@ -18,117 +22,51 @@ pub const Mutex = @import("Thread/Mutex.zig"); | ... | @@ -18,117 +22,51 @@ pub const Mutex = @import("Thread/Mutex.zig"); |
| 18 | pub const Semaphore = @import("Thread/Semaphore.zig"); | 22 | pub const Semaphore = @import("Thread/Semaphore.zig"); |
| 19 | pub const Condition = @import("Thread/Condition.zig"); | 23 | pub const Condition = @import("Thread/Condition.zig"); |
| 20 | 24 | ||
| 21 | pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; | 25 | pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint"); |
| 22 | 26 | ||
| 23 | const Thread = @This(); | 27 | pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc; |
| 24 | const std = @import("std.zig"); | ||
| 25 | const builtin = std.builtin; | ||
| 26 | const os = std.os; | ||
| 27 | const mem = std.mem; | ||
| 28 | const windows = std.os.windows; | ||
| 29 | const c = std.c; | ||
| 30 | const assert = std.debug.assert; | ||
| 31 | 28 | ||
| 32 | const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; | 29 | const Thread = @This(); |
| 30 | const Impl = if (target.os.tag == .windows) | ||
| 31 | WindowsThreadImpl | ||
| 32 | else if (use_pthreads) | ||
| 33 | PosixThreadImpl | ||
| 34 | else if (target.os.tag == .linux) | ||
| 35 | LinuxThreadImpl | ||
| 36 | else | ||
| 37 | UnsupportedImpl; | ||
| 33 | 38 | ||
| 34 | /// Represents a kernel thread handle. | 39 | impl: Impl, |
| 35 | /// May be an integer or a pointer depending on the platform. | ||
| 36 | /// On Linux and POSIX, this is the same as Id. | ||
| 37 | pub const Handle = if (use_pthreads) | ||
| 38 | c.pthread_t | ||
| 39 | else switch (std.Target.current.os.tag) { | ||
| 40 | .linux => i32, | ||
| 41 | .windows => windows.HANDLE, | ||
| 42 | else => void, | ||
| 43 | }; | ||
| 44 | 40 | ||
| 45 | /// Represents a unique ID per thread. | 41 | /// Represents a unique ID per thread. |
| 46 | /// May be an integer or pointer depending on the platform. | 42 | pub const Id = u64; |
| 47 | /// On Linux and POSIX, this is the same as Handle. | ||
| 48 | pub const Id = switch (std.Target.current.os.tag) { | ||
| 49 | .windows => windows.DWORD, | ||
| 50 | else => Handle, | ||
| 51 | }; | ||
| 52 | 43 | ||
| 53 | pub const Data = if (use_pthreads) | 44 | /// Returns the platform ID of the callers thread. |
| 54 | struct { | 45 | /// Attempts to use thread locals and avoid syscalls when possible. |
| 55 | handle: Thread.Handle, | ||
| 56 | memory: []u8, | ||
| 57 | } | ||
| 58 | else switch (std.Target.current.os.tag) { | ||
| 59 | .linux => struct { | ||
| 60 | handle: Thread.Handle, | ||
| 61 | memory: []align(mem.page_size) u8, | ||
| 62 | }, | ||
| 63 | .windows => struct { | ||
| 64 | handle: Thread.Handle, | ||
| 65 | alloc_start: *c_void, | ||
| 66 | heap_handle: windows.HANDLE, | ||
| 67 | }, | ||
| 68 | else => struct {}, | ||
| 69 | }; | ||
| 70 | |||
| 71 | pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint"); | ||
| 72 | |||
| 73 | /// Returns the ID of the calling thread. | ||
| 74 | /// Makes a syscall every time the function is called. | ||
| 75 | /// On Linux and POSIX, this Id is the same as a Handle. | ||
| 76 | pub fn getCurrentId() Id { | 46 | pub fn getCurrentId() Id { |
| 77 | if (use_pthreads) { | 47 | return Impl.getCurrentId(); |
| 78 | return c.pthread_self(); | ||
| 79 | } else return switch (std.Target.current.os.tag) { | ||
| 80 | .linux => os.linux.gettid(), | ||
| 81 | .windows => windows.kernel32.GetCurrentThreadId(), | ||
| 82 | else => @compileError("Unsupported OS"), | ||
| 83 | }; | ||
| 84 | } | 48 | } |
| 85 | 49 | ||
| 86 | /// Returns the handle of this thread. | 50 | pub const CpuCountError = error{ |
| 87 | /// On Linux and POSIX, this is the same as Id. | 51 | PermissionDenied, |
| 88 | /// On Linux, it is possible that the thread spawned with `spawn` | 52 | SystemResources, |
| 89 | /// finishes executing entirely before the clone syscall completes. In this | 53 | Unexpected, |
| 90 | /// case, this function will return 0 rather than the no-longer-existing thread's | 54 | }; |
| 91 | /// pid. | ||
| 92 | pub fn handle(self: Thread) Handle { | ||
| 93 | return self.data.handle; | ||
| 94 | } | ||
| 95 | 55 | ||
| 96 | pub fn wait(self: *Thread) void { | 56 | /// Returns the platforms view on the number of logical CPU cores available. |
| 97 | if (use_pthreads) { | 57 | pub fn getCpuCount() CpuCountError!usize { |
| 98 | const err = c.pthread_join(self.data.handle, null); | 58 | return Impl.getCpuCount(); |
| 99 | switch (err) { | ||
| 100 | 0 => {}, | ||
| 101 | os.EINVAL => unreachable, | ||
| 102 | os.ESRCH => unreachable, | ||
| 103 | os.EDEADLK => unreachable, | ||
| 104 | else => unreachable, | ||
| 105 | } | ||
| 106 | std.heap.c_allocator.free(self.data.memory); | ||
| 107 | std.heap.c_allocator.destroy(self); | ||
| 108 | } else switch (std.Target.current.os.tag) { | ||
| 109 | .linux => { | ||
| 110 | while (true) { | ||
| 111 | const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); | ||
| 112 | if (pid_value == 0) break; | ||
| 113 | const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null); | ||
| 114 | switch (os.linux.getErrno(rc)) { | ||
| 115 | 0 => continue, | ||
| 116 | os.EINTR => continue, | ||
| 117 | os.EAGAIN => continue, | ||
| 118 | else => unreachable, | ||
| 119 | } | ||
| 120 | } | ||
| 121 | os.munmap(self.data.memory); | ||
| 122 | }, | ||
| 123 | .windows => { | ||
| 124 | windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable; | ||
| 125 | windows.CloseHandle(self.data.handle); | ||
| 126 | windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start); | ||
| 127 | }, | ||
| 128 | else => @compileError("Unsupported OS"), | ||
| 129 | } | ||
| 130 | } | 59 | } |
| 131 | 60 | ||
| 61 | /// Configuration options for hints on how to spawn threads. | ||
| 62 | pub const SpawnConfig = struct { | ||
| 63 | // TODO compile-time call graph analysis to determine stack upper bound | ||
| 64 | // https://github.com/ziglang/zig/issues/157 | ||
| 65 | |||
| 66 | /// Size in bytes of the Thread's stack | ||
| 67 | stack_size: usize = 16 * 1024 * 1024, | ||
| 68 | }; | ||
| 69 | |||
| 132 | pub const SpawnError = error{ | 70 | pub const SpawnError = error{ |
| 133 | /// A system-imposed limit on the number of threads was encountered. | 71 | /// A system-imposed limit on the number of threads was encountered. |
| 134 | /// There are a number of limits that may trigger this error: | 72 | /// There are a number of limits that may trigger this error: |
| ... | @@ -159,248 +97,552 @@ pub const SpawnError = error{ | ... | @@ -159,248 +97,552 @@ pub const SpawnError = error{ |
| 159 | Unexpected, | 97 | Unexpected, |
| 160 | }; | 98 | }; |
| 161 | 99 | ||
| 162 | // Given `T`, the type of the thread startFn, extract the expected type for the | 100 | /// Spawns a new thread which executes `function` using `args` and returns a handle the spawned thread. |
| 163 | // context parameter. | 101 | /// `config` can be used as hints to the platform for now to spawn and execute the `function`. |
| 164 | fn SpawnContextType(comptime T: type) type { | 102 | /// The caller must eventually either call `join()` to wait for the thread to finish and free its resources |
| 165 | const TI = @typeInfo(T); | 103 | /// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`. |
| 166 | if (TI != .Fn) | 104 | pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread { |
| 167 | @compileError("expected function type, found " ++ @typeName(T)); | 105 | if (std.builtin.single_threaded) { |
| 106 | @compileError("Cannot spawn thread when building in single-threaded mode"); | ||
| 107 | } | ||
| 108 | |||
| 109 | const impl = try Impl.spawn(config, function, args); | ||
| 110 | return Thread{ .impl = impl }; | ||
| 111 | } | ||
| 168 | 112 | ||
| 169 | if (TI.Fn.args.len != 1) | 113 | /// Represents a kernel thread handle. |
| 170 | @compileError("expected function with single argument, found " ++ @typeName(T)); | 114 | /// May be an integer or a pointer depending on the platform. |
| 115 | pub const Handle = Impl.ThreadHandle; | ||
| 171 | 116 | ||
| 172 | return TI.Fn.args[0].arg_type orelse | 117 | /// Retrns the handle of this thread |
| 173 | @compileError("cannot use a generic function as thread startFn"); | 118 | pub fn getHandle(self: Thread) Handle { |
| 119 | return self.impl.getHandle(); | ||
| 174 | } | 120 | } |
| 175 | 121 | ||
| 176 | /// Spawns a new thread executing startFn, returning an handle for it. | 122 | /// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion. |
| 177 | /// Caller must call wait on the returned thread. | 123 | /// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior. |
| 178 | /// The `startFn` function must take a single argument of type T and return a | 124 | pub fn detach(self: Thread) void { |
| 179 | /// value of type u8, noreturn, void or !void. | 125 | return self.impl.detach(); |
| 180 | /// The `context` parameter is of type T and is passed to the spawned thread. | 126 | } |
| 181 | pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startFn))) SpawnError!*Thread { | ||
| 182 | if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); | ||
| 183 | // TODO compile-time call graph analysis to determine stack upper bound | ||
| 184 | // https://github.com/ziglang/zig/issues/157 | ||
| 185 | const default_stack_size = 16 * 1024 * 1024; | ||
| 186 | 127 | ||
| 187 | const Context = @TypeOf(context); | 128 | /// 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. | ||
| 130 | pub fn join(self: Thread) void { | ||
| 131 | return self.impl.join(); | ||
| 132 | } | ||
| 188 | 133 | ||
| 189 | if (std.Target.current.os.tag == .windows) { | 134 | /// State to synchronize detachment of spawner thread to spawned thread |
| 190 | const WinThread = struct { | 135 | const Completion = Atomic(enum(u8) { |
| 191 | const OuterContext = struct { | 136 | running, |
| 192 | thread: Thread, | 137 | detached, |
| 193 | inner: Context, | 138 | completed, |
| 194 | }; | 139 | }); |
| 195 | fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { | 140 | |
| 196 | const arg = if (@sizeOf(Context) == 0) undefined // | 141 | /// Used by the Thread implementations to call the spawned function with the arguments. |
| 197 | else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; | 142 | fn callFn(comptime f: anytype, args: anytype) switch (Impl) { |
| 198 | 143 | WindowsThreadImpl => std.os.windows.DWORD, | |
| 199 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | 144 | LinuxThreadImpl => u8, |
| 200 | .NoReturn => { | 145 | PosixThreadImpl => ?*c_void, |
| 201 | startFn(arg); | 146 | else => unreachable, |
| 202 | }, | 147 | } { |
| 203 | .Void => { | 148 | const default_value = if (Impl == PosixThreadImpl) null else 0; |
| 204 | startFn(arg); | 149 | const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; |
| 205 | return 0; | 150 | |
| 206 | }, | 151 | switch (@typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?)) { |
| 207 | .Int => |info| { | 152 | .NoReturn => { |
| 208 | if (info.bits != 8) { | 153 | @call(.{}, f, args); |
| 209 | @compileError(bad_startfn_ret); | 154 | }, |
| 210 | } | 155 | .Void => { |
| 211 | return startFn(arg); | 156 | @call(.{}, f, args); |
| 212 | }, | 157 | return default_value; |
| 213 | .ErrorUnion => |info| { | 158 | }, |
| 214 | if (info.payload != void) { | 159 | .Int => |info| { |
| 215 | @compileError(bad_startfn_ret); | 160 | if (info.bits != 8) { |
| 216 | } | 161 | @compileError(bad_fn_ret); |
| 217 | startFn(arg) catch |err| { | 162 | } |
| 218 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | 163 | |
| 219 | if (@errorReturnTrace()) |trace| { | 164 | const status = @call(.{}, f, args); |
| 220 | std.debug.dumpStackTrace(trace.*); | 165 | if (Impl != PosixThreadImpl) { |
| 221 | } | 166 | return status; |
| 222 | }; | 167 | } |
| 223 | return 0; | 168 | |
| 224 | }, | 169 | // pthreads don't support exit status, ignore value |
| 225 | else => @compileError(bad_startfn_ret), | 170 | _ = status; |
| 171 | return default_value; | ||
| 172 | }, | ||
| 173 | .ErrorUnion => |info| { | ||
| 174 | if (info.payload != void) { | ||
| 175 | @compileError(bad_fn_ret); | ||
| 176 | } | ||
| 177 | |||
| 178 | @call(.{}, f, args) catch |err| { | ||
| 179 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | ||
| 180 | if (@errorReturnTrace()) |trace| { | ||
| 181 | std.debug.dumpStackTrace(trace.*); | ||
| 226 | } | 182 | } |
| 183 | }; | ||
| 184 | |||
| 185 | return default_value; | ||
| 186 | }, | ||
| 187 | else => { | ||
| 188 | @compileError(bad_fn_ret); | ||
| 189 | }, | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 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. | ||
| 195 | const UnsupportedImpl = struct { | ||
| 196 | pub const ThreadHandle = void; | ||
| 197 | |||
| 198 | fn getCurrentId() u64 { | ||
| 199 | return unsupported({}); | ||
| 200 | } | ||
| 201 | |||
| 202 | fn getCpuCount() !usize { | ||
| 203 | return unsupported({}); | ||
| 204 | } | ||
| 205 | |||
| 206 | fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { | ||
| 207 | return unsupported(.{ config, f, args }); | ||
| 208 | } | ||
| 209 | |||
| 210 | fn getHandle(self: Impl) ThreadHandle { | ||
| 211 | return unsupported(self); | ||
| 212 | } | ||
| 213 | |||
| 214 | fn detach(self: Impl) void { | ||
| 215 | return unsupported(self); | ||
| 216 | } | ||
| 217 | |||
| 218 | fn join(self: Impl) void { | ||
| 219 | return unsupported(self); | ||
| 220 | } | ||
| 221 | |||
| 222 | fn unsupported(unusued: anytype) noreturn { | ||
| 223 | @compileLog("Unsupported operating system", target.os.tag); | ||
| 224 | _ = unusued; | ||
| 225 | unreachable; | ||
| 226 | } | ||
| 227 | }; | ||
| 228 | |||
| 229 | const WindowsThreadImpl = struct { | ||
| 230 | const windows = os.windows; | ||
| 231 | |||
| 232 | pub const ThreadHandle = windows.HANDLE; | ||
| 233 | |||
| 234 | fn getCurrentId() u64 { | ||
| 235 | return windows.kernel32.GetCurrentThreadId(); | ||
| 236 | } | ||
| 237 | |||
| 238 | fn getCpuCount() !usize { | ||
| 239 | // Faster than calling into GetSystemInfo(), even if amortized. | ||
| 240 | return windows.peb().NumberOfProcessors; | ||
| 241 | } | ||
| 242 | |||
| 243 | thread: *ThreadCompletion, | ||
| 244 | |||
| 245 | const ThreadCompletion = struct { | ||
| 246 | completion: Completion, | ||
| 247 | heap_ptr: windows.PVOID, | ||
| 248 | heap_handle: windows.HANDLE, | ||
| 249 | thread_handle: windows.HANDLE = undefined, | ||
| 250 | |||
| 251 | fn free(self: ThreadCompletion) void { | ||
| 252 | const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr); | ||
| 253 | assert(status != 0); | ||
| 254 | } | ||
| 255 | }; | ||
| 256 | |||
| 257 | fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { | ||
| 258 | const Args = @TypeOf(args); | ||
| 259 | const Instance = struct { | ||
| 260 | fn_args: Args, | ||
| 261 | thread: ThreadCompletion, | ||
| 262 | |||
| 263 | fn entryFn(raw_ptr: windows.PVOID) callconv(.C) windows.DWORD { | ||
| 264 | const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), raw_ptr)); | ||
| 265 | defer switch (self.thread.completion.swap(.completed, .SeqCst)) { | ||
| 266 | .running => {}, | ||
| 267 | .completed => unreachable, | ||
| 268 | .detached => self.thread.free(), | ||
| 269 | }; | ||
| 270 | return callFn(f, self.fn_args); | ||
| 227 | } | 271 | } |
| 228 | }; | 272 | }; |
| 229 | 273 | ||
| 230 | const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; | 274 | const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; |
| 231 | const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); | 275 | const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance); |
| 232 | const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; | 276 | const alloc_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory; |
| 233 | errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0); | 277 | errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0); |
| 234 | const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; | 278 | |
| 235 | const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; | 279 | const instance_bytes = @ptrCast([*]u8, alloc_ptr)[0..alloc_bytes]; |
| 236 | outer_context.* = WinThread.OuterContext{ | 280 | const instance = std.heap.FixedBufferAllocator.init(instance_bytes).allocator.create(Instance) catch unreachable; |
| 237 | .thread = Thread{ | 281 | instance.* = .{ |
| 238 | .data = Thread.Data{ | 282 | .fn_args = args, |
| 239 | .heap_handle = heap_handle, | 283 | .thread = .{ |
| 240 | .alloc_start = bytes_ptr, | 284 | .completion = Completion.init(.running), |
| 241 | .handle = undefined, | 285 | .heap_ptr = alloc_ptr, |
| 242 | }, | 286 | .heap_handle = heap_handle, |
| 243 | }, | 287 | }, |
| 244 | .inner = context, | ||
| 245 | }; | 288 | }; |
| 246 | 289 | ||
| 247 | const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); | 290 | // Windows appears to only support SYSTEM_INFO.dwAllocationGranularity minimum stack size. |
| 248 | outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { | 291 | // Going lower makes it default to that specified in the executable (~1mb). |
| 249 | switch (windows.kernel32.GetLastError()) { | 292 | // Its also fine if the limit here is incorrect as stack size is only a hint. |
| 250 | else => |err| return windows.unexpectedError(err), | 293 | var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32); |
| 251 | } | 294 | stack_size = std.math.max(64 * 1024, stack_size); |
| 295 | |||
| 296 | instance.thread.thread_handle = windows.kernel32.CreateThread( | ||
| 297 | null, | ||
| 298 | stack_size, | ||
| 299 | Instance.entryFn, | ||
| 300 | @ptrCast(*c_void, instance), | ||
| 301 | 0, | ||
| 302 | null, | ||
| 303 | ) orelse { | ||
| 304 | const errno = windows.kernel32.GetLastError(); | ||
| 305 | return windows.unexpectedError(errno); | ||
| 252 | }; | 306 | }; |
| 253 | return &outer_context.thread; | 307 | |
| 308 | return Impl{ .thread = &instance.thread }; | ||
| 254 | } | 309 | } |
| 255 | 310 | ||
| 256 | const MainFuncs = struct { | 311 | fn getHandle(self: Impl) ThreadHandle { |
| 257 | fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { | 312 | return self.thread.thread_handle; |
| 258 | const arg = if (@sizeOf(Context) == 0) undefined // | 313 | } |
| 259 | else @intToPtr(*Context, ctx_addr).*; | ||
| 260 | 314 | ||
| 261 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | 315 | fn detach(self: Impl) void { |
| 262 | .NoReturn => { | 316 | windows.CloseHandle(self.thread.thread_handle); |
| 263 | startFn(arg); | 317 | switch (self.thread.completion.swap(.detached, .SeqCst)) { |
| 264 | }, | 318 | .running => {}, |
| 265 | .Void => { | 319 | .completed => self.thread.free(), |
| 266 | startFn(arg); | 320 | .detached => unreachable, |
| 267 | return 0; | ||
| 268 | }, | ||
| 269 | .Int => |info| { | ||
| 270 | if (info.bits != 8) { | ||
| 271 | @compileError(bad_startfn_ret); | ||
| 272 | } | ||
| 273 | return startFn(arg); | ||
| 274 | }, | ||
| 275 | .ErrorUnion => |info| { | ||
| 276 | if (info.payload != void) { | ||
| 277 | @compileError(bad_startfn_ret); | ||
| 278 | } | ||
| 279 | startFn(arg) catch |err| { | ||
| 280 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | ||
| 281 | if (@errorReturnTrace()) |trace| { | ||
| 282 | std.debug.dumpStackTrace(trace.*); | ||
| 283 | } | ||
| 284 | }; | ||
| 285 | return 0; | ||
| 286 | }, | ||
| 287 | else => @compileError(bad_startfn_ret), | ||
| 288 | } | ||
| 289 | } | 321 | } |
| 290 | fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { | 322 | } |
| 291 | const arg = if (@sizeOf(Context) == 0) undefined // | ||
| 292 | else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; | ||
| 293 | 323 | ||
| 294 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | 324 | fn join(self: Impl) void { |
| 295 | .NoReturn => { | 325 | windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable; |
| 296 | startFn(arg); | 326 | windows.CloseHandle(self.thread.thread_handle); |
| 297 | }, | 327 | assert(self.thread.completion.load(.SeqCst) == .completed); |
| 298 | .Void => { | 328 | self.thread.free(); |
| 299 | startFn(arg); | 329 | } |
| 300 | return null; | 330 | }; |
| 301 | }, | 331 | |
| 302 | .Int => |info| { | 332 | const PosixThreadImpl = struct { |
| 303 | if (info.bits != 8) { | 333 | const c = std.c; |
| 304 | @compileError(bad_startfn_ret); | 334 | |
| 305 | } | 335 | pub const ThreadHandle = c.pthread_t; |
| 306 | // pthreads don't support exit status, ignore value | 336 | |
| 307 | _ = startFn(arg); | 337 | fn getCurrentId() Id { |
| 308 | return null; | 338 | switch (target.os.tag) { |
| 309 | }, | 339 | .linux => { |
| 310 | .ErrorUnion => |info| { | 340 | return LinuxThreadImpl.getCurrentId(); |
| 311 | if (info.payload != void) { | 341 | }, |
| 312 | @compileError(bad_startfn_ret); | 342 | .macos, .ios, .watchos, .tvos => { |
| 313 | } | 343 | var thread_id: u64 = undefined; |
| 314 | startFn(arg) catch |err| { | 344 | // Pass thread=null to get the current thread ID. |
| 315 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | 345 | assert(c.pthread_threadid_np(null, &thread_id) == 0); |
| 316 | if (@errorReturnTrace()) |trace| { | 346 | return thread_id; |
| 317 | std.debug.dumpStackTrace(trace.*); | 347 | }, |
| 318 | } | 348 | .dragonfly => { |
| 319 | }; | 349 | return @bitCast(u32, c.lwp_gettid()); |
| 320 | return null; | 350 | }, |
| 321 | }, | 351 | .netbsd => { |
| 322 | else => @compileError(bad_startfn_ret), | 352 | return @bitCast(u32, c._lwp_self()); |
| 323 | } | 353 | }, |
| 354 | .freebsd => { | ||
| 355 | return @bitCast(u32, c.pthread_getthreadid_np()); | ||
| 356 | }, | ||
| 357 | .openbsd => { | ||
| 358 | return @bitCast(u32, c.getthrid()); | ||
| 359 | }, | ||
| 360 | .haiku => { | ||
| 361 | return @bitCast(u32, c.find_thread(null)); | ||
| 362 | }, | ||
| 363 | else => { | ||
| 364 | return @ptrToInt(c.pthread_self()); | ||
| 365 | }, | ||
| 324 | } | 366 | } |
| 325 | }; | 367 | } |
| 368 | |||
| 369 | fn getCpuCount() !usize { | ||
| 370 | switch (target.os.tag) { | ||
| 371 | .linux => { | ||
| 372 | return LinuxThreadImpl.getCpuCount(); | ||
| 373 | }, | ||
| 374 | .openbsd => { | ||
| 375 | var count: c_int = undefined; | ||
| 376 | var count_size: usize = @sizeOf(c_int); | ||
| 377 | const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; | ||
| 378 | os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { | ||
| 379 | error.NameTooLong, error.UnknownName => unreachable, | ||
| 380 | else => |e| return e, | ||
| 381 | }; | ||
| 382 | return @intCast(usize, count); | ||
| 383 | }, | ||
| 384 | .haiku => { | ||
| 385 | var count: u32 = undefined; | ||
| 386 | var system_info: os.system_info = undefined; | ||
| 387 | _ = os.system.get_system_info(&system_info); // always returns B_OK | ||
| 388 | count = system_info.cpu_count; | ||
| 389 | return @intCast(usize, count); | ||
| 390 | }, | ||
| 391 | else => { | ||
| 392 | var count: c_int = undefined; | ||
| 393 | var count_len: usize = @sizeOf(c_int); | ||
| 394 | const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; | ||
| 395 | os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { | ||
| 396 | error.NameTooLong, error.UnknownName => unreachable, | ||
| 397 | else => |e| return e, | ||
| 398 | }; | ||
| 399 | return @intCast(usize, count); | ||
| 400 | }, | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | handle: ThreadHandle, | ||
| 405 | |||
| 406 | fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { | ||
| 407 | const Args = @TypeOf(args); | ||
| 408 | const allocator = std.heap.c_allocator; | ||
| 409 | |||
| 410 | const Instance = struct { | ||
| 411 | fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void { | ||
| 412 | // @alignCast() below doesn't support zero-sized-types (ZST) | ||
| 413 | if (@sizeOf(Args) < 1) { | ||
| 414 | return callFn(f, @as(Args, undefined)); | ||
| 415 | } | ||
| 416 | |||
| 417 | const args_ptr = @ptrCast(*Args, @alignCast(@alignOf(Args), raw_arg)); | ||
| 418 | defer allocator.destroy(args_ptr); | ||
| 419 | return callFn(f, args_ptr.*); | ||
| 420 | } | ||
| 421 | }; | ||
| 422 | |||
| 423 | const args_ptr = try allocator.create(Args); | ||
| 424 | args_ptr.* = args; | ||
| 425 | errdefer allocator.destroy(args_ptr); | ||
| 326 | 426 | ||
| 327 | if (Thread.use_pthreads) { | ||
| 328 | var attr: c.pthread_attr_t = undefined; | 427 | var attr: c.pthread_attr_t = undefined; |
| 329 | if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; | 428 | if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; |
| 330 | defer assert(c.pthread_attr_destroy(&attr) == 0); | 429 | defer assert(c.pthread_attr_destroy(&attr) == 0); |
| 331 | 430 | ||
| 332 | const thread_obj = try std.heap.c_allocator.create(Thread); | ||
| 333 | errdefer std.heap.c_allocator.destroy(thread_obj); | ||
| 334 | if (@sizeOf(Context) > 0) { | ||
| 335 | thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced( | ||
| 336 | u8, | ||
| 337 | @alignOf(Context), | ||
| 338 | @sizeOf(Context), | ||
| 339 | .at_least, | ||
| 340 | ); | ||
| 341 | errdefer std.heap.c_allocator.free(thread_obj.data.memory); | ||
| 342 | mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context)); | ||
| 343 | } else { | ||
| 344 | thread_obj.data.memory = @as([*]u8, undefined)[0..0]; | ||
| 345 | } | ||
| 346 | |||
| 347 | // Use the same set of parameters used by the libc-less impl. | 431 | // Use the same set of parameters used by the libc-less impl. |
| 348 | assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0); | 432 | const stack_size = std.math.max(config.stack_size, 16 * 1024); |
| 349 | assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0); | 433 | assert(c.pthread_attr_setstacksize(&attr, stack_size) == 0); |
| 434 | assert(c.pthread_attr_setguardsize(&attr, std.mem.page_size) == 0); | ||
| 350 | 435 | ||
| 351 | const err = c.pthread_create( | 436 | var handle: c.pthread_t = undefined; |
| 352 | &thread_obj.data.handle, | 437 | switch (c.pthread_create( |
| 438 | &handle, | ||
| 353 | &attr, | 439 | &attr, |
| 354 | MainFuncs.posixThreadMain, | 440 | Instance.entryFn, |
| 355 | thread_obj.data.memory.ptr, | 441 | if (@sizeOf(Args) > 1) @ptrCast(*c_void, args_ptr) else undefined, |
| 356 | ); | 442 | )) { |
| 357 | switch (err) { | 443 | 0 => return Impl{ .handle = handle }, |
| 358 | 0 => return thread_obj, | ||
| 359 | os.EAGAIN => return error.SystemResources, | 444 | os.EAGAIN => return error.SystemResources, |
| 360 | os.EPERM => unreachable, | 445 | os.EPERM => unreachable, |
| 361 | os.EINVAL => unreachable, | 446 | os.EINVAL => unreachable, |
| 362 | else => return os.unexpectedErrno(err), | 447 | else => |err| return os.unexpectedErrno(err), |
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | fn getHandle(self: Impl) ThreadHandle { | ||
| 452 | return self.handle; | ||
| 453 | } | ||
| 454 | |||
| 455 | fn detach(self: Impl) void { | ||
| 456 | switch (c.pthread_detach(self.handle)) { | ||
| 457 | 0 => {}, | ||
| 458 | os.EINVAL => unreachable, // thread handle is not joinable | ||
| 459 | os.ESRCH => unreachable, // thread handle is invalid | ||
| 460 | else => unreachable, | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | fn join(self: Impl) void { | ||
| 465 | switch (c.pthread_join(self.handle, null)) { | ||
| 466 | 0 => {}, | ||
| 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 | ||
| 470 | else => unreachable, | ||
| 363 | } | 471 | } |
| 472 | } | ||
| 473 | }; | ||
| 474 | |||
| 475 | const LinuxThreadImpl = struct { | ||
| 476 | const linux = os.linux; | ||
| 364 | 477 | ||
| 365 | return thread_obj; | 478 | pub const ThreadHandle = i32; |
| 479 | |||
| 480 | threadlocal var tls_thread_id: ?Id = null; | ||
| 481 | |||
| 482 | fn getCurrentId() Id { | ||
| 483 | return tls_thread_id orelse { | ||
| 484 | const tid = @bitCast(u32, linux.gettid()); | ||
| 485 | tls_thread_id = tid; | ||
| 486 | return tid; | ||
| 487 | }; | ||
| 366 | } | 488 | } |
| 367 | 489 | ||
| 368 | var guard_end_offset: usize = undefined; | 490 | fn getCpuCount() !usize { |
| 369 | var stack_end_offset: usize = undefined; | 491 | const cpu_set = try os.sched_getaffinity(0); |
| 370 | var thread_start_offset: usize = undefined; | 492 | // TODO: should not need this usize cast |
| 371 | var context_start_offset: usize = undefined; | 493 | return @as(usize, os.CPU_COUNT(cpu_set)); |
| 372 | var tls_start_offset: usize = undefined; | 494 | } |
| 373 | const mmap_len = blk: { | 495 | |
| 374 | var l: usize = mem.page_size; | 496 | thread: *ThreadCompletion, |
| 375 | // Allocate a guard page right after the end of the stack region | 497 | |
| 376 | guard_end_offset = l; | 498 | const ThreadCompletion = struct { |
| 377 | // The stack itself, which grows downwards. | 499 | completion: Completion = Completion.init(.running), |
| 378 | l = mem.alignForward(l + default_stack_size, mem.page_size); | 500 | child_tid: Atomic(i32) = Atomic(i32).init(1), |
| 379 | stack_end_offset = l; | 501 | parent_tid: i32 = undefined, |
| 380 | // Above the stack, so that it can be in the same mmap call, put the Thread object. | 502 | mapped: []align(std.mem.page_size) u8, |
| 381 | l = mem.alignForward(l, @alignOf(Thread)); | 503 | |
| 382 | thread_start_offset = l; | 504 | /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`). |
| 383 | l += @sizeOf(Thread); | 505 | /// Ported over from musl libc's pthread detached implementation: |
| 384 | // Next, the Context object. | 506 | /// https://github.com/ifduyue/musl/search?q=__unmapself |
| 385 | if (@sizeOf(Context) != 0) { | 507 | fn freeAndExit(self: *ThreadCompletion) noreturn { |
| 386 | l = mem.alignForward(l, @alignOf(Context)); | 508 | const unmap_and_exit: []const u8 = switch (target.cpu.arch) { |
| 387 | context_start_offset = l; | 509 | .i386 => ( |
| 388 | l += @sizeOf(Context); | 510 | \\ movl $91, %%eax |
| 511 | \\ movl %[ptr], %%ebx | ||
| 512 | \\ movl %[len], %%ecx | ||
| 513 | \\ int $128 | ||
| 514 | \\ movl $1, %%eax | ||
| 515 | \\ movl $0, %%ebx | ||
| 516 | \\ int $128 | ||
| 517 | ), | ||
| 518 | .x86_64 => ( | ||
| 519 | \\ movq $11, %%rax | ||
| 520 | \\ movq %[ptr], %%rbx | ||
| 521 | \\ movq %[len], %%rcx | ||
| 522 | \\ syscall | ||
| 523 | \\ movq $60, %%rax | ||
| 524 | \\ movq $1, %%rdi | ||
| 525 | \\ syscall | ||
| 526 | ), | ||
| 527 | .arm, .armeb, .thumb, .thumbeb => ( | ||
| 528 | \\ mov r7, #91 | ||
| 529 | \\ mov r0, %[ptr] | ||
| 530 | \\ mov r1, %[len] | ||
| 531 | \\ svc 0 | ||
| 532 | \\ mov r7, #1 | ||
| 533 | \\ mov r0, #0 | ||
| 534 | \\ svc 0 | ||
| 535 | ), | ||
| 536 | .aarch64, .aarch64_be, .aarch64_32 => ( | ||
| 537 | \\ mov x8, #215 | ||
| 538 | \\ mov x0, %[ptr] | ||
| 539 | \\ mov x1, %[len] | ||
| 540 | \\ svc 0 | ||
| 541 | \\ mov x8, #93 | ||
| 542 | \\ mov x0, #0 | ||
| 543 | \\ svc 0 | ||
| 544 | ), | ||
| 545 | .mips, .mipsel => ( | ||
| 546 | \\ move $sp, $25 | ||
| 547 | \\ li $2, 4091 | ||
| 548 | \\ move $4, %[ptr] | ||
| 549 | \\ move $5, %[len] | ||
| 550 | \\ syscall | ||
| 551 | \\ li $2, 4001 | ||
| 552 | \\ li $4, 0 | ||
| 553 | \\ syscall | ||
| 554 | ), | ||
| 555 | .mips64, .mips64el => ( | ||
| 556 | \\ li $2, 4091 | ||
| 557 | \\ move $4, %[ptr] | ||
| 558 | \\ move $5, %[len] | ||
| 559 | \\ syscall | ||
| 560 | \\ li $2, 4001 | ||
| 561 | \\ li $4, 0 | ||
| 562 | \\ syscall | ||
| 563 | ), | ||
| 564 | .powerpc, .powerpcle, .powerpc64, .powerpc64le => ( | ||
| 565 | \\ li 0, 91 | ||
| 566 | \\ mr %[ptr], 3 | ||
| 567 | \\ mr %[len], 4 | ||
| 568 | \\ sc | ||
| 569 | \\ li 0, 1 | ||
| 570 | \\ li 3, 0 | ||
| 571 | \\ sc | ||
| 572 | \\ blr | ||
| 573 | ), | ||
| 574 | .riscv64 => ( | ||
| 575 | \\ li a7, 215 | ||
| 576 | \\ mv a0, %[ptr] | ||
| 577 | \\ mv a1, %[len] | ||
| 578 | \\ ecall | ||
| 579 | \\ li a7, 93 | ||
| 580 | \\ mv a0, zero | ||
| 581 | \\ ecall | ||
| 582 | ), | ||
| 583 | else => |cpu_arch| { | ||
| 584 | @compileLog("Unsupported linux arch ", cpu_arch); | ||
| 585 | }, | ||
| 586 | }; | ||
| 587 | |||
| 588 | asm volatile (unmap_and_exit | ||
| 589 | : | ||
| 590 | : [ptr] "r" (@ptrToInt(self.mapped.ptr)), | ||
| 591 | [len] "r" (self.mapped.len) | ||
| 592 | : "memory" | ||
| 593 | ); | ||
| 594 | |||
| 595 | unreachable; | ||
| 389 | } | 596 | } |
| 390 | // Finally, the Thread Local Storage, if any. | ||
| 391 | l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align); | ||
| 392 | tls_start_offset = l; | ||
| 393 | l += os.linux.tls.tls_image.alloc_size; | ||
| 394 | // Round the size to the page size. | ||
| 395 | break :blk mem.alignForward(l, mem.page_size); | ||
| 396 | }; | 597 | }; |
| 397 | 598 | ||
| 398 | const mmap_slice = mem: { | 599 | fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { |
| 399 | // Map the whole stack with no rw permissions to avoid | 600 | const Args = @TypeOf(args); |
| 400 | // committing the whole region right away | 601 | const Instance = struct { |
| 401 | const mmap_slice = os.mmap( | 602 | fn_args: Args, |
| 603 | thread: ThreadCompletion, | ||
| 604 | |||
| 605 | fn entryFn(raw_arg: usize) callconv(.C) u8 { | ||
| 606 | const self = @intToPtr(*@This(), raw_arg); | ||
| 607 | defer switch (self.thread.completion.swap(.completed, .SeqCst)) { | ||
| 608 | .running => {}, | ||
| 609 | .completed => unreachable, | ||
| 610 | .detached => self.thread.freeAndExit(), | ||
| 611 | }; | ||
| 612 | return callFn(f, self.fn_args); | ||
| 613 | } | ||
| 614 | }; | ||
| 615 | |||
| 616 | var guard_offset: usize = undefined; | ||
| 617 | var stack_offset: usize = undefined; | ||
| 618 | var tls_offset: usize = undefined; | ||
| 619 | var instance_offset: usize = undefined; | ||
| 620 | |||
| 621 | const map_bytes = blk: { | ||
| 622 | var bytes: usize = std.mem.page_size; | ||
| 623 | guard_offset = bytes; | ||
| 624 | |||
| 625 | bytes += std.math.max(std.mem.page_size, config.stack_size); | ||
| 626 | bytes = std.mem.alignForward(bytes, std.mem.page_size); | ||
| 627 | stack_offset = bytes; | ||
| 628 | |||
| 629 | bytes = std.mem.alignForward(bytes, linux.tls.tls_image.alloc_align); | ||
| 630 | tls_offset = bytes; | ||
| 631 | bytes += linux.tls.tls_image.alloc_size; | ||
| 632 | |||
| 633 | bytes = std.mem.alignForward(bytes, @alignOf(Instance)); | ||
| 634 | instance_offset = bytes; | ||
| 635 | bytes += @sizeOf(Instance); | ||
| 636 | |||
| 637 | bytes = std.mem.alignForward(bytes, std.mem.page_size); | ||
| 638 | break :blk bytes; | ||
| 639 | }; | ||
| 640 | |||
| 641 | // map all memory needed without read/write permissions | ||
| 642 | // to avoid committing the whole region right away | ||
| 643 | const mapped = os.mmap( | ||
| 402 | null, | 644 | null, |
| 403 | mmap_len, | 645 | map_bytes, |
| 404 | os.PROT_NONE, | 646 | os.PROT_NONE, |
| 405 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, | 647 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, |
| 406 | -1, | 648 | -1, |
| ... | @@ -411,73 +653,57 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF | ... | @@ -411,73 +653,57 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF |
| 411 | error.PermissionDenied => unreachable, | 653 | error.PermissionDenied => unreachable, |
| 412 | else => |e| return e, | 654 | else => |e| return e, |
| 413 | }; | 655 | }; |
| 414 | errdefer os.munmap(mmap_slice); | 656 | assert(mapped.len >= map_bytes); |
| 657 | errdefer os.munmap(mapped); | ||
| 415 | 658 | ||
| 416 | // Map everything but the guard page as rw | 659 | // map everything but the guard page as read/write |
| 417 | os.mprotect( | 660 | os.mprotect( |
| 418 | mmap_slice[guard_end_offset..], | 661 | mapped[guard_offset..], |
| 419 | os.PROT_READ | os.PROT_WRITE, | 662 | os.PROT_READ | os.PROT_WRITE, |
| 420 | ) catch |err| switch (err) { | 663 | ) catch |err| switch (err) { |
| 421 | error.AccessDenied => unreachable, | 664 | error.AccessDenied => unreachable, |
| 422 | else => |e| return e, | 665 | else => |e| return e, |
| 423 | }; | 666 | }; |
| 424 | 667 | ||
| 425 | break :mem mmap_slice; | 668 | // Prepare the TLS segment and prepare a user_desc struct when needed on i386 |
| 426 | }; | 669 | var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]); |
| 427 | 670 | var user_desc: if (target.cpu.arch == .i386) os.linux.user_desc else void = undefined; | |
| 428 | const mmap_addr = @ptrToInt(mmap_slice.ptr); | 671 | if (target.cpu.arch == .i386) { |
| 429 | 672 | defer tls_ptr = @ptrToInt(&user_desc); | |
| 430 | const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); | 673 | user_desc = .{ |
| 431 | thread_ptr.data.memory = mmap_slice; | 674 | .entry_number = os.linux.tls.tls_image.gdt_entry_number, |
| 675 | .base_addr = tls_ptr, | ||
| 676 | .limit = 0xfffff, | ||
| 677 | .seg_32bit = 1, | ||
| 678 | .contents = 0, // Data | ||
| 679 | .read_exec_only = 0, | ||
| 680 | .limit_in_pages = 1, | ||
| 681 | .seg_not_present = 0, | ||
| 682 | .useable = 1, | ||
| 683 | }; | ||
| 684 | } | ||
| 432 | 685 | ||
| 433 | var arg: usize = undefined; | 686 | const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &mapped[instance_offset])); |
| 434 | if (@sizeOf(Context) != 0) { | 687 | instance.* = .{ |
| 435 | arg = mmap_addr + context_start_offset; | 688 | .fn_args = args, |
| 436 | const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); | 689 | .thread = .{ .mapped = mapped }, |
| 437 | context_ptr.* = context; | 690 | }; |
| 438 | } | ||
| 439 | 691 | ||
| 440 | if (std.Target.current.os.tag == .linux) { | 692 | const flags: u32 = os.CLONE_THREAD | os.CLONE_DETACHED | |
| 441 | const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | | 693 | os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | |
| 442 | os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | | ||
| 443 | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | | 694 | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | |
| 444 | os.CLONE_DETACHED | os.CLONE_SETTLS; | 695 | os.CLONE_SIGHAND | os.CLONE_SYSVSEM | os.CLONE_SETTLS; |
| 445 | // This structure is only needed when targeting i386 | ||
| 446 | var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; | ||
| 447 | |||
| 448 | const tls_area = mmap_slice[tls_start_offset..]; | ||
| 449 | const tp_value = os.linux.tls.prepareTLS(tls_area); | ||
| 450 | |||
| 451 | const newtls = blk: { | ||
| 452 | if (std.Target.current.cpu.arch == .i386) { | ||
| 453 | user_desc = os.linux.user_desc{ | ||
| 454 | .entry_number = os.linux.tls.tls_image.gdt_entry_number, | ||
| 455 | .base_addr = tp_value, | ||
| 456 | .limit = 0xfffff, | ||
| 457 | .seg_32bit = 1, | ||
| 458 | .contents = 0, // Data | ||
| 459 | .read_exec_only = 0, | ||
| 460 | .limit_in_pages = 1, | ||
| 461 | .seg_not_present = 0, | ||
| 462 | .useable = 1, | ||
| 463 | }; | ||
| 464 | break :blk @ptrToInt(&user_desc); | ||
| 465 | } else { | ||
| 466 | break :blk tp_value; | ||
| 467 | } | ||
| 468 | }; | ||
| 469 | 696 | ||
| 470 | const rc = os.linux.clone( | 697 | switch (linux.getErrno(linux.clone( |
| 471 | MainFuncs.linuxThreadMain, | 698 | Instance.entryFn, |
| 472 | mmap_addr + stack_end_offset, | 699 | @ptrToInt(&mapped[stack_offset]), |
| 473 | flags, | 700 | flags, |
| 474 | arg, | 701 | @ptrToInt(instance), |
| 475 | &thread_ptr.data.handle, | 702 | &instance.thread.parent_tid, |
| 476 | newtls, | 703 | tls_ptr, |
| 477 | &thread_ptr.data.handle, | 704 | &instance.thread.child_tid.value, |
| 478 | ); | 705 | ))) { |
| 479 | switch (os.errno(rc)) { | 706 | 0 => return Impl{ .thread = &instance.thread }, |
| 480 | 0 => return thread_ptr, | ||
| 481 | os.EAGAIN => return error.ThreadQuotaExceeded, | 707 | os.EAGAIN => return error.ThreadQuotaExceeded, |
| 482 | os.EINVAL => unreachable, | 708 | os.EINVAL => unreachable, |
| 483 | os.ENOMEM => return error.SystemResources, | 709 | os.ENOMEM => return error.SystemResources, |
| ... | @@ -486,100 +712,92 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF | ... | @@ -486,100 +712,92 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF |
| 486 | os.EUSERS => unreachable, | 712 | os.EUSERS => unreachable, |
| 487 | else => |err| return os.unexpectedErrno(err), | 713 | else => |err| return os.unexpectedErrno(err), |
| 488 | } | 714 | } |
| 489 | } else { | ||
| 490 | @compileError("Unsupported OS"); | ||
| 491 | } | 715 | } |
| 492 | } | ||
| 493 | 716 | ||
| 494 | pub const CpuCountError = error{ | 717 | fn getHandle(self: Impl) ThreadHandle { |
| 495 | PermissionDenied, | 718 | return self.thread.parent_tid; |
| 496 | SystemResources, | 719 | } |
| 497 | Unexpected, | ||
| 498 | }; | ||
| 499 | 720 | ||
| 500 | pub fn cpuCount() CpuCountError!usize { | 721 | fn detach(self: Impl) void { |
| 501 | switch (std.Target.current.os.tag) { | 722 | switch (self.thread.completion.swap(.detached, .SeqCst)) { |
| 502 | .linux => { | 723 | .running => {}, |
| 503 | const cpu_set = try os.sched_getaffinity(0); | 724 | .completed => self.join(), |
| 504 | return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast | 725 | .detached => unreachable, |
| 505 | }, | 726 | } |
| 506 | .windows => { | ||
| 507 | return os.windows.peb().NumberOfProcessors; | ||
| 508 | }, | ||
| 509 | .openbsd => { | ||
| 510 | var count: c_int = undefined; | ||
| 511 | var count_size: usize = @sizeOf(c_int); | ||
| 512 | const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; | ||
| 513 | os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { | ||
| 514 | error.NameTooLong, error.UnknownName => unreachable, | ||
| 515 | else => |e| return e, | ||
| 516 | }; | ||
| 517 | return @intCast(usize, count); | ||
| 518 | }, | ||
| 519 | .haiku => { | ||
| 520 | var count: u32 = undefined; | ||
| 521 | // var system_info: os.system_info = undefined; | ||
| 522 | // const rc = os.system.get_system_info(&system_info); | ||
| 523 | count = system_info.cpu_count; | ||
| 524 | return @intCast(usize, count); | ||
| 525 | }, | ||
| 526 | else => { | ||
| 527 | var count: c_int = undefined; | ||
| 528 | var count_len: usize = @sizeOf(c_int); | ||
| 529 | const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; | ||
| 530 | os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { | ||
| 531 | error.NameTooLong, error.UnknownName => unreachable, | ||
| 532 | else => |e| return e, | ||
| 533 | }; | ||
| 534 | return @intCast(usize, count); | ||
| 535 | }, | ||
| 536 | } | 727 | } |
| 537 | } | ||
| 538 | 728 | ||
| 539 | pub fn getCurrentThreadId() u64 { | 729 | fn join(self: Impl) void { |
| 540 | switch (std.Target.current.os.tag) { | 730 | defer os.munmap(self.thread.mapped); |
| 541 | .linux => { | 731 | |
| 542 | // Use the syscall directly as musl doesn't provide a wrapper. | 732 | var spin: u8 = 10; |
| 543 | return @bitCast(u32, os.linux.gettid()); | 733 | while (true) { |
| 544 | }, | 734 | const tid = self.thread.child_tid.load(.SeqCst); |
| 545 | .windows => { | 735 | if (tid == 0) { |
| 546 | return os.windows.kernel32.GetCurrentThreadId(); | 736 | break; |
| 547 | }, | 737 | } |
| 548 | .macos, .ios, .watchos, .tvos => { | 738 | |
| 549 | var thread_id: u64 = undefined; | 739 | if (spin > 0) { |
| 550 | // Pass thread=null to get the current thread ID. | 740 | spin -= 1; |
| 551 | assert(c.pthread_threadid_np(null, &thread_id) == 0); | 741 | std.atomic.spinLoopHint(); |
| 552 | return thread_id; | 742 | continue; |
| 553 | }, | 743 | } |
| 554 | .dragonfly => { | 744 | |
| 555 | return @bitCast(u32, c.lwp_gettid()); | 745 | switch (linux.getErrno(linux.futex_wait( |
| 556 | }, | 746 | &self.thread.child_tid.value, |
| 557 | .netbsd => { | 747 | linux.FUTEX_WAIT, |
| 558 | return @bitCast(u32, c._lwp_self()); | 748 | tid, |
| 559 | }, | 749 | null, |
| 560 | .freebsd => { | 750 | ))) { |
| 561 | return @bitCast(u32, c.pthread_getthreadid_np()); | 751 | 0 => continue, |
| 562 | }, | 752 | os.EINTR => continue, |
| 563 | .openbsd => { | 753 | os.EAGAIN => continue, |
| 564 | return @bitCast(u32, c.getthrid()); | 754 | else => unreachable, |
| 565 | }, | 755 | } |
| 566 | .haiku => { | 756 | } |
| 567 | return @bitCast(u32, c.find_thread(null)); | ||
| 568 | }, | ||
| 569 | else => { | ||
| 570 | @compileError("getCurrentThreadId not implemented for this platform"); | ||
| 571 | }, | ||
| 572 | } | 757 | } |
| 573 | } | 758 | }; |
| 574 | 759 | ||
| 575 | test "std.Thread" { | 760 | test "std.Thread" { |
| 576 | if (!builtin.single_threaded) { | 761 | // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint. |
| 577 | _ = AutoResetEvent; | 762 | _ = AutoResetEvent; |
| 578 | _ = Futex; | 763 | _ = Futex; |
| 579 | _ = ResetEvent; | 764 | _ = ResetEvent; |
| 580 | _ = StaticResetEvent; | 765 | _ = StaticResetEvent; |
| 581 | _ = Mutex; | 766 | _ = Mutex; |
| 582 | _ = Semaphore; | 767 | _ = Semaphore; |
| 583 | _ = Condition; | 768 | _ = Condition; |
| 584 | } | 769 | } |
| 770 | |||
| 771 | fn testIncrementNotify(value: *usize, event: *ResetEvent) void { | ||
| 772 | value.* += 1; | ||
| 773 | event.set(); | ||
| 774 | } | ||
| 775 | |||
| 776 | test "Thread.join" { | ||
| 777 | if (std.builtin.single_threaded) return error.SkipZigTest; | ||
| 778 | |||
| 779 | var value: usize = 0; | ||
| 780 | var event: ResetEvent = undefined; | ||
| 781 | try event.init(); | ||
| 782 | defer event.deinit(); | ||
| 783 | |||
| 784 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); | ||
| 785 | thread.join(); | ||
| 786 | |||
| 787 | try std.testing.expectEqual(value, 1); | ||
| 788 | } | ||
| 789 | |||
| 790 | test "Thread.detach" { | ||
| 791 | if (std.builtin.single_threaded) return error.SkipZigTest; | ||
| 792 | |||
| 793 | var value: usize = 0; | ||
| 794 | var event: ResetEvent = undefined; | ||
| 795 | try event.init(); | ||
| 796 | defer event.deinit(); | ||
| 797 | |||
| 798 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); | ||
| 799 | thread.detach(); | ||
| 800 | |||
| 801 | event.wait(); | ||
| 802 | try std.testing.expectEqual(value, 1); | ||
| 585 | } | 803 | } |
lib/std/Thread/AutoResetEvent.zig+4-4| ... | @@ -220,9 +220,9 @@ test "basic usage" { | ... | @@ -220,9 +220,9 @@ test "basic usage" { |
| 220 | }; | 220 | }; |
| 221 | 221 | ||
| 222 | var context = Context{}; | 222 | var context = Context{}; |
| 223 | const send_thread = try std.Thread.spawn(Context.sender, &context); | 223 | const send_thread = try std.Thread.spawn(.{}, Context.sender, .{&context}); |
| 224 | const recv_thread = try std.Thread.spawn(Context.receiver, &context); | 224 | const recv_thread = try std.Thread.spawn(.{}, Context.receiver, .{&context}); |
| 225 | 225 | ||
| 226 | send_thread.wait(); | 226 | send_thread.join(); |
| 227 | recv_thread.wait(); | 227 | recv_thread.join(); |
| 228 | } | 228 | } |
lib/std/Thread/Futex.zig+116-124| ... | @@ -64,9 +64,8 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut} | ... | @@ -64,9 +64,8 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut} |
| 64 | /// Unblocks at most `num_waiters` callers blocked in a `wait()` call on `ptr`. | 64 | /// Unblocks at most `num_waiters` callers blocked in a `wait()` call on `ptr`. |
| 65 | /// `num_waiters` of 1 unblocks at most one `wait(ptr, ...)` and `maxInt(u32)` unblocks effectively all `wait(ptr, ...)`. | 65 | /// `num_waiters` of 1 unblocks at most one `wait(ptr, ...)` and `maxInt(u32)` unblocks effectively all `wait(ptr, ...)`. |
| 66 | pub fn wake(ptr: *const Atomic(u32), num_waiters: u32) void { | 66 | pub fn wake(ptr: *const Atomic(u32), num_waiters: u32) void { |
| 67 | if (num_waiters == 0 or single_threaded) { | 67 | if (single_threaded) return; |
| 68 | return; | 68 | if (num_waiters == 0) return; |
| 69 | } | ||
| 70 | 69 | ||
| 71 | return OsFutex.wake(ptr, num_waiters); | 70 | return OsFutex.wake(ptr, num_waiters); |
| 72 | } | 71 | } |
| ... | @@ -80,7 +79,23 @@ else if (target.isDarwin()) | ... | @@ -80,7 +79,23 @@ else if (target.isDarwin()) |
| 80 | else if (std.builtin.link_libc) | 79 | else if (std.builtin.link_libc) |
| 81 | PosixFutex | 80 | PosixFutex |
| 82 | else | 81 | else |
| 83 | @compileError("Operating System unsupported"); | 82 | UnsupportedFutex; |
| 83 | |||
| 84 | const UnsupportedFutex = struct { | ||
| 85 | fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}!void { | ||
| 86 | return unsupported(.{ ptr, expect, timeout }); | ||
| 87 | } | ||
| 88 | |||
| 89 | fn wake(ptr: *const Atomic(u32), num_waiters: u32) void { | ||
| 90 | return unsupported(.{ ptr, num_waiters }); | ||
| 91 | } | ||
| 92 | |||
| 93 | fn unsupported(unused: anytype) noreturn { | ||
| 94 | @compileLog("Unsupported operating system", target.os.tag); | ||
| 95 | _ = unused; | ||
| 96 | unreachable; | ||
| 97 | } | ||
| 98 | }; | ||
| 84 | 99 | ||
| 85 | const WindowsFutex = struct { | 100 | const WindowsFutex = struct { |
| 86 | const windows = std.os.windows; | 101 | const windows = std.os.windows; |
| ... | @@ -391,75 +406,73 @@ test "Futex - wait/wake" { | ... | @@ -391,75 +406,73 @@ test "Futex - wait/wake" { |
| 391 | } | 406 | } |
| 392 | 407 | ||
| 393 | test "Futex - Signal" { | 408 | test "Futex - Signal" { |
| 394 | if (!single_threaded) { | 409 | if (single_threaded) { |
| 395 | return; | 410 | return error.SkipZigTest; |
| 396 | } | 411 | } |
| 397 | 412 | ||
| 398 | try (struct { | 413 | const Paddle = struct { |
| 399 | value: Atomic(u32) = Atomic(u32).init(0), | 414 | value: Atomic(u32) = Atomic(u32).init(0), |
| 415 | current: u32 = 0, | ||
| 400 | 416 | ||
| 401 | const Self = @This(); | 417 | fn run(self: *@This(), hit_to: *@This()) !void { |
| 418 | var iterations: usize = 4; | ||
| 419 | while (iterations > 0) : (iterations -= 1) { | ||
| 420 | var value: u32 = undefined; | ||
| 421 | while (true) { | ||
| 422 | value = self.value.load(.Acquire); | ||
| 423 | if (value != self.current) break; | ||
| 424 | Futex.wait(&self.value, self.current, null) catch unreachable; | ||
| 425 | } | ||
| 402 | 426 | ||
| 403 | fn send(self: *Self, value: u32) void { | 427 | try testing.expectEqual(value, self.current + 1); |
| 404 | self.value.store(value, .Release); | 428 | self.current = value; |
| 405 | Futex.wake(&self.value, 1); | ||
| 406 | } | ||
| 407 | 429 | ||
| 408 | fn recv(self: *Self, expected: u32) void { | 430 | _ = hit_to.value.fetchAdd(1, .Release); |
| 409 | while (true) { | 431 | Futex.wake(&hit_to.value, 1); |
| 410 | const value = self.value.load(.Acquire); | ||
| 411 | if (value == expected) break; | ||
| 412 | Futex.wait(&self.value, value, null) catch unreachable; | ||
| 413 | } | 432 | } |
| 414 | } | 433 | } |
| 434 | }; | ||
| 415 | 435 | ||
| 416 | const Thread = struct { | 436 | var ping = Paddle{}; |
| 417 | tx: *Self, | 437 | var pong = Paddle{}; |
| 418 | rx: *Self, | ||
| 419 | |||
| 420 | const start_value = 1; | ||
| 421 | |||
| 422 | fn run(self: Thread) void { | ||
| 423 | var iterations: u32 = start_value; | ||
| 424 | while (iterations < 10) : (iterations += 1) { | ||
| 425 | self.rx.recv(iterations); | ||
| 426 | self.tx.send(iterations); | ||
| 427 | } | ||
| 428 | } | ||
| 429 | }; | ||
| 430 | |||
| 431 | fn run() !void { | ||
| 432 | var ping = Self{}; | ||
| 433 | var pong = Self{}; | ||
| 434 | 438 | ||
| 435 | const t1 = try std.Thread.spawn(Thread.run, .{ .rx = &ping, .tx = &pong }); | 439 | const t1 = try std.Thread.spawn(.{}, Paddle.run, .{ &ping, &pong }); |
| 436 | defer t1.wait(); | 440 | defer t1.join(); |
| 437 | 441 | ||
| 438 | const t2 = try std.Thread.spawn(Thread.run, .{ .rx = &pong, .tx = &ping }); | 442 | const t2 = try std.Thread.spawn(.{}, Paddle.run, .{ &pong, &ping }); |
| 439 | defer t2.wait(); | 443 | defer t2.join(); |
| 440 | 444 | ||
| 441 | ping.send(Thread.start_value); | 445 | _ = ping.value.fetchAdd(1, .Release); |
| 442 | } | 446 | Futex.wake(&ping.value, 1); |
| 443 | }).run(); | ||
| 444 | } | 447 | } |
| 445 | 448 | ||
| 446 | test "Futex - Broadcast" { | 449 | test "Futex - Broadcast" { |
| 447 | if (!single_threaded) { | 450 | if (single_threaded) { |
| 448 | return; | 451 | return error.SkipZigTest; |
| 449 | } | 452 | } |
| 450 | 453 | ||
| 451 | try (struct { | 454 | const Context = struct { |
| 452 | threads: [10]*std.Thread = undefined, | 455 | threads: [4]std.Thread = undefined, |
| 453 | broadcast: Atomic(u32) = Atomic(u32).init(0), | 456 | broadcast: Atomic(u32) = Atomic(u32).init(0), |
| 454 | notified: Atomic(usize) = Atomic(usize).init(0), | 457 | notified: Atomic(usize) = Atomic(usize).init(0), |
| 455 | 458 | ||
| 456 | const Self = @This(); | ||
| 457 | |||
| 458 | const BROADCAST_EMPTY = 0; | 459 | const BROADCAST_EMPTY = 0; |
| 459 | const BROADCAST_SENT = 1; | 460 | const BROADCAST_SENT = 1; |
| 460 | const BROADCAST_RECEIVED = 2; | 461 | const BROADCAST_RECEIVED = 2; |
| 461 | 462 | ||
| 462 | fn runReceiver(self: *Self) void { | 463 | fn runSender(self: *@This()) !void { |
| 464 | self.broadcast.store(BROADCAST_SENT, .Monotonic); | ||
| 465 | Futex.wake(&self.broadcast, @intCast(u32, self.threads.len)); | ||
| 466 | |||
| 467 | while (true) { | ||
| 468 | const broadcast = self.broadcast.load(.Acquire); | ||
| 469 | if (broadcast == BROADCAST_RECEIVED) break; | ||
| 470 | try testing.expectEqual(broadcast, BROADCAST_SENT); | ||
| 471 | Futex.wait(&self.broadcast, broadcast, null) catch unreachable; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | fn runReceiver(self: *@This()) void { | ||
| 463 | while (true) { | 476 | while (true) { |
| 464 | const broadcast = self.broadcast.load(.Acquire); | 477 | const broadcast = self.broadcast.load(.Acquire); |
| 465 | if (broadcast == BROADCAST_SENT) break; | 478 | if (broadcast == BROADCAST_SENT) break; |
| ... | @@ -473,98 +486,77 @@ test "Futex - Broadcast" { | ... | @@ -473,98 +486,77 @@ test "Futex - Broadcast" { |
| 473 | Futex.wake(&self.broadcast, 1); | 486 | Futex.wake(&self.broadcast, 1); |
| 474 | } | 487 | } |
| 475 | } | 488 | } |
| 489 | }; | ||
| 476 | 490 | ||
| 477 | fn run() !void { | 491 | var ctx = Context{}; |
| 478 | var self = Self{}; | 492 | for (ctx.threads) |*thread| |
| 479 | 493 | thread.* = try std.Thread.spawn(.{}, Context.runReceiver, .{&ctx}); | |
| 480 | for (self.threads) |*thread| | 494 | defer for (ctx.threads) |thread| |
| 481 | thread.* = try std.Thread.spawn(runReceiver, &self); | 495 | thread.join(); |
| 482 | defer for (self.threads) |thread| | ||
| 483 | thread.wait(); | ||
| 484 | 496 | ||
| 485 | std.time.sleep(16 * std.time.ns_per_ms); | 497 | // Try to wait for the threads to start before running runSender(). |
| 486 | self.broadcast.store(BROADCAST_SENT, .Monotonic); | 498 | // NOTE: not actually needed for correctness. |
| 487 | Futex.wake(&self.broadcast, @intCast(u32, self.threads.len)); | 499 | std.time.sleep(16 * std.time.ns_per_ms); |
| 500 | try ctx.runSender(); | ||
| 488 | 501 | ||
| 489 | while (true) { | 502 | const notified = ctx.notified.load(.Monotonic); |
| 490 | const broadcast = self.broadcast.load(.Acquire); | 503 | try testing.expectEqual(notified, ctx.threads.len); |
| 491 | if (broadcast == BROADCAST_RECEIVED) break; | ||
| 492 | try testing.expectEqual(broadcast, BROADCAST_SENT); | ||
| 493 | Futex.wait(&self.broadcast, broadcast, null) catch unreachable; | ||
| 494 | } | ||
| 495 | |||
| 496 | const notified = self.notified.load(.Monotonic); | ||
| 497 | try testing.expectEqual(notified, self.threads.len); | ||
| 498 | } | ||
| 499 | }).run(); | ||
| 500 | } | 504 | } |
| 501 | 505 | ||
| 502 | test "Futex - Chain" { | 506 | test "Futex - Chain" { |
| 503 | if (!single_threaded) { | 507 | if (single_threaded) { |
| 504 | return; | 508 | return error.SkipZigTest; |
| 505 | } | 509 | } |
| 506 | 510 | ||
| 507 | try (struct { | 511 | const Signal = struct { |
| 508 | completed: Signal = .{}, | 512 | value: Atomic(u32) = Atomic(u32).init(0), |
| 509 | threads: [10]struct { | ||
| 510 | thread: *std.Thread, | ||
| 511 | signal: Signal, | ||
| 512 | } = undefined, | ||
| 513 | |||
| 514 | const Signal = struct { | ||
| 515 | state: Atomic(u32) = Atomic(u32).init(0), | ||
| 516 | |||
| 517 | fn wait(self: *Signal) void { | ||
| 518 | while (true) { | ||
| 519 | const value = self.value.load(.Acquire); | ||
| 520 | if (value == 1) break; | ||
| 521 | assert(value == 0); | ||
| 522 | Futex.wait(&self.value, 0, null) catch unreachable; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | 513 | ||
| 526 | fn notify(self: *Signal) void { | 514 | fn wait(self: *@This()) void { |
| 527 | assert(self.value.load(.Unordered) == 0); | 515 | while (true) { |
| 528 | self.value.store(1, .Release); | 516 | const value = self.value.load(.Acquire); |
| 529 | Futex.wake(&self.value, 1); | 517 | if (value == 1) break; |
| 518 | assert(value == 0); | ||
| 519 | Futex.wait(&self.value, 0, null) catch unreachable; | ||
| 530 | } | 520 | } |
| 531 | }; | 521 | } |
| 532 | 522 | ||
| 533 | const Self = @This(); | 523 | fn notify(self: *@This()) void { |
| 534 | const Chain = struct { | 524 | assert(self.value.load(.Unordered) == 0); |
| 535 | self: *Self, | 525 | self.value.store(1, .Release); |
| 536 | index: usize, | 526 | Futex.wake(&self.value, 1); |
| 527 | } | ||
| 528 | }; | ||
| 537 | 529 | ||
| 538 | fn run(chain: Chain) void { | 530 | const Context = struct { |
| 539 | const this_signal = &chain.self.threads[chain.index].signal; | 531 | completed: Signal = .{}, |
| 532 | threads: [4]struct { | ||
| 533 | thread: std.Thread, | ||
| 534 | signal: Signal, | ||
| 535 | } = undefined, | ||
| 540 | 536 | ||
| 541 | var next_signal = &chain.self.completed; | 537 | fn run(self: *@This(), index: usize) void { |
| 542 | if (chain.index + 1 < chain.self.threads.len) { | 538 | const this_signal = &self.threads[index].signal; |
| 543 | next_signal = &chain.self.threads[chain.index + 1].signal; | ||
| 544 | } | ||
| 545 | 539 | ||
| 546 | this_signal.wait(); | 540 | var next_signal = &self.completed; |
| 547 | next_signal.notify(); | 541 | if (index + 1 < self.threads.len) { |
| 542 | next_signal = &self.threads[index + 1].signal; | ||
| 548 | } | 543 | } |
| 549 | }; | ||
| 550 | 544 | ||
| 551 | fn run() !void { | 545 | this_signal.wait(); |
| 552 | var self = Self{}; | 546 | next_signal.notify(); |
| 547 | } | ||
| 548 | }; | ||
| 553 | 549 | ||
| 554 | for (self.threads) |*entry, index| { | 550 | var ctx = Context{}; |
| 555 | entry.signal = .{}; | 551 | for (ctx.threads) |*entry, index| { |
| 556 | entry.thread = try std.Thread.spawn(Chain.run, .{ | 552 | entry.signal = .{}; |
| 557 | .self = &self, | 553 | entry.thread = try std.Thread.spawn(.{}, Context.run, .{ &ctx, index }); |
| 558 | .index = index, | 554 | } |
| 559 | }); | ||
| 560 | } | ||
| 561 | 555 | ||
| 562 | self.threads[0].signal.notify(); | 556 | ctx.threads[0].signal.notify(); |
| 563 | self.completed.wait(); | 557 | ctx.completed.wait(); |
| 564 | 558 | ||
| 565 | for (self.threads) |entry| { | 559 | for (ctx.threads) |entry| { |
| 566 | entry.thread.wait(); | 560 | entry.thread.join(); |
| 567 | } | 561 | } |
| 568 | } | ||
| 569 | }).run(); | ||
| 570 | } | 562 | } |
lib/std/Thread/Mutex.zig+3-3| ... | @@ -297,12 +297,12 @@ test "basic usage" { | ... | @@ -297,12 +297,12 @@ test "basic usage" { |
| 297 | try testing.expect(context.data == TestContext.incr_count); | 297 | try testing.expect(context.data == TestContext.incr_count); |
| 298 | } else { | 298 | } else { |
| 299 | const thread_count = 10; | 299 | const thread_count = 10; |
| 300 | var threads: [thread_count]*std.Thread = undefined; | 300 | var threads: [thread_count]std.Thread = undefined; |
| 301 | for (threads) |*t| { | 301 | for (threads) |*t| { |
| 302 | t.* = try std.Thread.spawn(worker, &context); | 302 | t.* = try std.Thread.spawn(.{}, worker, .{&context}); |
| 303 | } | 303 | } |
| 304 | for (threads) |t| | 304 | for (threads) |t| |
| 305 | t.wait(); | 305 | t.join(); |
| 306 | 306 | ||
| 307 | try testing.expect(context.data == thread_count * TestContext.incr_count); | 307 | try testing.expect(context.data == thread_count * TestContext.incr_count); |
| 308 | } | 308 | } |
lib/std/Thread/ResetEvent.zig+4-4| ... | @@ -281,8 +281,8 @@ test "basic usage" { | ... | @@ -281,8 +281,8 @@ test "basic usage" { |
| 281 | var context: Context = undefined; | 281 | var context: Context = undefined; |
| 282 | try context.init(); | 282 | try context.init(); |
| 283 | defer context.deinit(); | 283 | defer context.deinit(); |
| 284 | const receiver = try std.Thread.spawn(Context.receiver, &context); | 284 | const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context}); |
| 285 | defer receiver.wait(); | 285 | defer receiver.join(); |
| 286 | try context.sender(); | 286 | try context.sender(); |
| 287 | 287 | ||
| 288 | if (false) { | 288 | if (false) { |
| ... | @@ -290,8 +290,8 @@ test "basic usage" { | ... | @@ -290,8 +290,8 @@ test "basic usage" { |
| 290 | // https://github.com/ziglang/zig/issues/7009 | 290 | // https://github.com/ziglang/zig/issues/7009 |
| 291 | var timed = Context.init(); | 291 | var timed = Context.init(); |
| 292 | defer timed.deinit(); | 292 | defer timed.deinit(); |
| 293 | const sleeper = try std.Thread.spawn(Context.sleeper, &timed); | 293 | const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed}); |
| 294 | defer sleeper.wait(); | 294 | defer sleeper.join(); |
| 295 | try timed.timedWaiter(); | 295 | try timed.timedWaiter(); |
| 296 | } | 296 | } |
| 297 | } | 297 | } |
lib/std/Thread/StaticResetEvent.zig+4-4| ... | @@ -384,8 +384,8 @@ test "basic usage" { | ... | @@ -384,8 +384,8 @@ test "basic usage" { |
| 384 | }; | 384 | }; |
| 385 | 385 | ||
| 386 | var context = Context{}; | 386 | var context = Context{}; |
| 387 | const receiver = try std.Thread.spawn(Context.receiver, &context); | 387 | const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context}); |
| 388 | defer receiver.wait(); | 388 | defer receiver.join(); |
| 389 | try context.sender(); | 389 | try context.sender(); |
| 390 | 390 | ||
| 391 | if (false) { | 391 | if (false) { |
| ... | @@ -393,8 +393,8 @@ test "basic usage" { | ... | @@ -393,8 +393,8 @@ test "basic usage" { |
| 393 | // https://github.com/ziglang/zig/issues/7009 | 393 | // https://github.com/ziglang/zig/issues/7009 |
| 394 | var timed = Context.init(); | 394 | var timed = Context.init(); |
| 395 | defer timed.deinit(); | 395 | defer timed.deinit(); |
| 396 | const sleeper = try std.Thread.spawn(Context.sleeper, &timed); | 396 | const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed}); |
| 397 | defer sleeper.wait(); | 397 | defer sleeper.join(); |
| 398 | try timed.timedWaiter(); | 398 | try timed.timedWaiter(); |
| 399 | } | 399 | } |
| 400 | } | 400 | } |
lib/std/atomic/queue.zig+6-6| ... | @@ -214,20 +214,20 @@ test "std.atomic.Queue" { | ... | @@ -214,20 +214,20 @@ test "std.atomic.Queue" { |
| 214 | } else { | 214 | } else { |
| 215 | try expect(context.queue.isEmpty()); | 215 | try expect(context.queue.isEmpty()); |
| 216 | 216 | ||
| 217 | var putters: [put_thread_count]*std.Thread = undefined; | 217 | var putters: [put_thread_count]std.Thread = undefined; |
| 218 | for (putters) |*t| { | 218 | for (putters) |*t| { |
| 219 | t.* = try std.Thread.spawn(startPuts, &context); | 219 | t.* = try std.Thread.spawn(.{}, startPuts, .{&context}); |
| 220 | } | 220 | } |
| 221 | var getters: [put_thread_count]*std.Thread = undefined; | 221 | var getters: [put_thread_count]std.Thread = undefined; |
| 222 | for (getters) |*t| { | 222 | for (getters) |*t| { |
| 223 | t.* = try std.Thread.spawn(startGets, &context); | 223 | t.* = try std.Thread.spawn(.{}, startGets, .{&context}); |
| 224 | } | 224 | } |
| 225 | 225 | ||
| 226 | for (putters) |t| | 226 | for (putters) |t| |
| 227 | t.wait(); | 227 | t.join(); |
| 228 | @atomicStore(bool, &context.puts_done, true, .SeqCst); | 228 | @atomicStore(bool, &context.puts_done, true, .SeqCst); |
| 229 | for (getters) |t| | 229 | for (getters) |t| |
| 230 | t.wait(); | 230 | t.join(); |
| 231 | 231 | ||
| 232 | try expect(context.queue.isEmpty()); | 232 | try expect(context.queue.isEmpty()); |
| 233 | } | 233 | } |
lib/std/atomic/stack.zig+6-6| ... | @@ -121,20 +121,20 @@ test "std.atomic.stack" { | ... | @@ -121,20 +121,20 @@ test "std.atomic.stack" { |
| 121 | } | 121 | } |
| 122 | } | 122 | } |
| 123 | } else { | 123 | } else { |
| 124 | var putters: [put_thread_count]*std.Thread = undefined; | 124 | var putters: [put_thread_count]std.Thread = undefined; |
| 125 | for (putters) |*t| { | 125 | for (putters) |*t| { |
| 126 | t.* = try std.Thread.spawn(startPuts, &context); | 126 | t.* = try std.Thread.spawn(.{}, startPuts, .{&context}); |
| 127 | } | 127 | } |
| 128 | var getters: [put_thread_count]*std.Thread = undefined; | 128 | var getters: [put_thread_count]std.Thread = undefined; |
| 129 | for (getters) |*t| { | 129 | for (getters) |*t| { |
| 130 | t.* = try std.Thread.spawn(startGets, &context); | 130 | t.* = try std.Thread.spawn(.{}, startGets, .{&context}); |
| 131 | } | 131 | } |
| 132 | 132 | ||
| 133 | for (putters) |t| | 133 | for (putters) |t| |
| 134 | t.wait(); | 134 | t.join(); |
| 135 | @atomicStore(bool, &context.puts_done, true, .SeqCst); | 135 | @atomicStore(bool, &context.puts_done, true, .SeqCst); |
| 136 | for (getters) |t| | 136 | for (getters) |t| |
| 137 | t.wait(); | 137 | t.join(); |
| 138 | } | 138 | } |
| 139 | 139 | ||
| 140 | if (context.put_sum != context.get_sum) { | 140 | if (context.put_sum != context.get_sum) { |
lib/std/c.zig+1| ... | @@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us | ... | @@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us |
| 277 | pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int; | 277 | pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int; |
| 278 | pub extern "c" fn pthread_self() pthread_t; | 278 | pub extern "c" fn pthread_self() pthread_t; |
| 279 | pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int; | 279 | pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int; |
| 280 | pub extern "c" fn pthread_detach(thread: pthread_t) c_int; | ||
| 280 | pub extern "c" fn pthread_atfork( | 281 | pub extern "c" fn pthread_atfork( |
| 281 | prepare: ?fn () callconv(.C) void, | 282 | prepare: ?fn () callconv(.C) void, |
| 282 | parent: ?fn () callconv(.C) void, | 283 | parent: ?fn () callconv(.C) void, |
lib/std/debug.zig+2-2| ... | @@ -273,8 +273,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c | ... | @@ -273,8 +273,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 273 | if (builtin.single_threaded) { | 273 | if (builtin.single_threaded) { |
| 274 | stderr.print("panic: ", .{}) catch os.abort(); | 274 | stderr.print("panic: ", .{}) catch os.abort(); |
| 275 | } else { | 275 | } else { |
| 276 | const current_thread_id = std.Thread.getCurrentThreadId(); | 276 | const current_thread_id = std.Thread.getCurrentId(); |
| 277 | stderr.print("thread {d} panic: ", .{current_thread_id}) catch os.abort(); | 277 | stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort(); |
| 278 | } | 278 | } |
| 279 | stderr.print(format ++ "\n", args) catch os.abort(); | 279 | stderr.print(format ++ "\n", args) catch os.abort(); |
| 280 | if (trace) |t| { | 280 | if (trace) |t| { |
lib/std/event/loop.zig+18-18| ... | @@ -21,12 +21,12 @@ pub const Loop = struct { | ... | @@ -21,12 +21,12 @@ pub const Loop = struct { |
| 21 | os_data: OsData, | 21 | os_data: OsData, |
| 22 | final_resume_node: ResumeNode, | 22 | final_resume_node: ResumeNode, |
| 23 | pending_event_count: usize, | 23 | pending_event_count: usize, |
| 24 | extra_threads: []*Thread, | 24 | extra_threads: []Thread, |
| 25 | /// TODO change this to a pool of configurable number of threads | 25 | /// TODO change this to a pool of configurable number of threads |
| 26 | /// and rename it to be not file-system-specific. it will become | 26 | /// and rename it to be not file-system-specific. it will become |
| 27 | /// a thread pool for turning non-CPU-bound blocking things into | 27 | /// a thread pool for turning non-CPU-bound blocking things into |
| 28 | /// async things. A fallback for any missing OS-specific API. | 28 | /// async things. A fallback for any missing OS-specific API. |
| 29 | fs_thread: *Thread, | 29 | fs_thread: Thread, |
| 30 | fs_queue: std.atomic.Queue(Request), | 30 | fs_queue: std.atomic.Queue(Request), |
| 31 | fs_end_request: Request.Node, | 31 | fs_end_request: Request.Node, |
| 32 | fs_thread_wakeup: std.Thread.ResetEvent, | 32 | fs_thread_wakeup: std.Thread.ResetEvent, |
| ... | @@ -137,7 +137,7 @@ pub const Loop = struct { | ... | @@ -137,7 +137,7 @@ pub const Loop = struct { |
| 137 | } | 137 | } |
| 138 | 138 | ||
| 139 | /// After initialization, call run(). | 139 | /// After initialization, call run(). |
| 140 | /// This is the same as `initThreadPool` using `Thread.cpuCount` to determine the thread | 140 | /// This is the same as `initThreadPool` using `Thread.getCpuCount` to determine the thread |
| 141 | /// pool size. | 141 | /// pool size. |
| 142 | /// TODO copy elision / named return values so that the threads referencing *Loop | 142 | /// TODO copy elision / named return values so that the threads referencing *Loop |
| 143 | /// have the correct pointer value. | 143 | /// have the correct pointer value. |
| ... | @@ -145,7 +145,7 @@ pub const Loop = struct { | ... | @@ -145,7 +145,7 @@ pub const Loop = struct { |
| 145 | pub fn initMultiThreaded(self: *Loop) !void { | 145 | pub fn initMultiThreaded(self: *Loop) !void { |
| 146 | if (builtin.single_threaded) | 146 | if (builtin.single_threaded) |
| 147 | @compileError("initMultiThreaded unavailable when building in single-threaded mode"); | 147 | @compileError("initMultiThreaded unavailable when building in single-threaded mode"); |
| 148 | const core_count = try Thread.cpuCount(); | 148 | const core_count = try Thread.getCpuCount(); |
| 149 | return self.initThreadPool(core_count); | 149 | return self.initThreadPool(core_count); |
| 150 | } | 150 | } |
| 151 | 151 | ||
| ... | @@ -183,17 +183,17 @@ pub const Loop = struct { | ... | @@ -183,17 +183,17 @@ pub const Loop = struct { |
| 183 | resume_node_count, | 183 | resume_node_count, |
| 184 | ); | 184 | ); |
| 185 | 185 | ||
| 186 | self.extra_threads = try self.arena.allocator.alloc(*Thread, extra_thread_count); | 186 | self.extra_threads = try self.arena.allocator.alloc(Thread, extra_thread_count); |
| 187 | 187 | ||
| 188 | try self.initOsData(extra_thread_count); | 188 | try self.initOsData(extra_thread_count); |
| 189 | errdefer self.deinitOsData(); | 189 | errdefer self.deinitOsData(); |
| 190 | 190 | ||
| 191 | if (!builtin.single_threaded) { | 191 | if (!builtin.single_threaded) { |
| 192 | self.fs_thread = try Thread.spawn(posixFsRun, self); | 192 | self.fs_thread = try Thread.spawn(.{}, posixFsRun, .{self}); |
| 193 | } | 193 | } |
| 194 | errdefer if (!builtin.single_threaded) { | 194 | errdefer if (!builtin.single_threaded) { |
| 195 | self.posixFsRequest(&self.fs_end_request); | 195 | self.posixFsRequest(&self.fs_end_request); |
| 196 | self.fs_thread.wait(); | 196 | self.fs_thread.join(); |
| 197 | }; | 197 | }; |
| 198 | 198 | ||
| 199 | if (!std.builtin.single_threaded) | 199 | if (!std.builtin.single_threaded) |
| ... | @@ -264,11 +264,11 @@ pub const Loop = struct { | ... | @@ -264,11 +264,11 @@ pub const Loop = struct { |
| 264 | assert(amt == wakeup_bytes.len); | 264 | assert(amt == wakeup_bytes.len); |
| 265 | while (extra_thread_index != 0) { | 265 | while (extra_thread_index != 0) { |
| 266 | extra_thread_index -= 1; | 266 | extra_thread_index -= 1; |
| 267 | self.extra_threads[extra_thread_index].wait(); | 267 | self.extra_threads[extra_thread_index].join(); |
| 268 | } | 268 | } |
| 269 | } | 269 | } |
| 270 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { | 270 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { |
| 271 | self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self); | 271 | self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self}); |
| 272 | } | 272 | } |
| 273 | }, | 273 | }, |
| 274 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { | 274 | .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { |
| ... | @@ -329,11 +329,11 @@ pub const Loop = struct { | ... | @@ -329,11 +329,11 @@ pub const Loop = struct { |
| 329 | _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable; | 329 | _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable; |
| 330 | while (extra_thread_index != 0) { | 330 | while (extra_thread_index != 0) { |
| 331 | extra_thread_index -= 1; | 331 | extra_thread_index -= 1; |
| 332 | self.extra_threads[extra_thread_index].wait(); | 332 | self.extra_threads[extra_thread_index].join(); |
| 333 | } | 333 | } |
| 334 | } | 334 | } |
| 335 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { | 335 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { |
| 336 | self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self); | 336 | self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self}); |
| 337 | } | 337 | } |
| 338 | }, | 338 | }, |
| 339 | .windows => { | 339 | .windows => { |
| ... | @@ -378,11 +378,11 @@ pub const Loop = struct { | ... | @@ -378,11 +378,11 @@ pub const Loop = struct { |
| 378 | } | 378 | } |
| 379 | while (extra_thread_index != 0) { | 379 | while (extra_thread_index != 0) { |
| 380 | extra_thread_index -= 1; | 380 | extra_thread_index -= 1; |
| 381 | self.extra_threads[extra_thread_index].wait(); | 381 | self.extra_threads[extra_thread_index].join(); |
| 382 | } | 382 | } |
| 383 | } | 383 | } |
| 384 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { | 384 | while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) { |
| 385 | self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self); | 385 | self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self}); |
| 386 | } | 386 | } |
| 387 | }, | 387 | }, |
| 388 | else => {}, | 388 | else => {}, |
| ... | @@ -651,18 +651,18 @@ pub const Loop = struct { | ... | @@ -651,18 +651,18 @@ pub const Loop = struct { |
| 651 | .netbsd, | 651 | .netbsd, |
| 652 | .dragonfly, | 652 | .dragonfly, |
| 653 | .openbsd, | 653 | .openbsd, |
| 654 | => self.fs_thread.wait(), | 654 | => self.fs_thread.join(), |
| 655 | else => {}, | 655 | else => {}, |
| 656 | } | 656 | } |
| 657 | } | 657 | } |
| 658 | 658 | ||
| 659 | for (self.extra_threads) |extra_thread| { | 659 | for (self.extra_threads) |extra_thread| { |
| 660 | extra_thread.wait(); | 660 | extra_thread.join(); |
| 661 | } | 661 | } |
| 662 | 662 | ||
| 663 | @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst); | 663 | @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst); |
| 664 | self.delay_queue.event.set(); | 664 | self.delay_queue.event.set(); |
| 665 | self.delay_queue.thread.wait(); | 665 | self.delay_queue.thread.join(); |
| 666 | } | 666 | } |
| 667 | 667 | ||
| 668 | /// Runs the provided function asynchronously. The function's frame is allocated | 668 | /// Runs the provided function asynchronously. The function's frame is allocated |
| ... | @@ -787,7 +787,7 @@ pub const Loop = struct { | ... | @@ -787,7 +787,7 @@ pub const Loop = struct { |
| 787 | const DelayQueue = struct { | 787 | const DelayQueue = struct { |
| 788 | timer: std.time.Timer, | 788 | timer: std.time.Timer, |
| 789 | waiters: Waiters, | 789 | waiters: Waiters, |
| 790 | thread: *std.Thread, | 790 | thread: std.Thread, |
| 791 | event: std.Thread.AutoResetEvent, | 791 | event: std.Thread.AutoResetEvent, |
| 792 | is_running: bool, | 792 | is_running: bool, |
| 793 | 793 | ||
| ... | @@ -802,7 +802,7 @@ pub const Loop = struct { | ... | @@ -802,7 +802,7 @@ pub const Loop = struct { |
| 802 | .event = std.Thread.AutoResetEvent{}, | 802 | .event = std.Thread.AutoResetEvent{}, |
| 803 | .is_running = true, | 803 | .is_running = true, |
| 804 | // Must be last so that it can read the other state, such as `is_running`. | 804 | // Must be last so that it can read the other state, such as `is_running`. |
| 805 | .thread = try std.Thread.spawn(DelayQueue.run, self), | 805 | .thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}), |
| 806 | }; | 806 | }; |
| 807 | } | 807 | } |
| 808 | 808 |
lib/std/fs/test.zig+5-6| ... | @@ -862,11 +862,10 @@ test "open file with exclusive lock twice, make sure it waits" { | ... | @@ -862,11 +862,10 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 862 | errdefer file.close(); | 862 | errdefer file.close(); |
| 863 | 863 | ||
| 864 | const S = struct { | 864 | const S = struct { |
| 865 | const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent }; | 865 | fn checkFn(dir: *fs.Dir, evt: *std.Thread.ResetEvent) !void { |
| 866 | fn checkFn(ctx: C) !void { | 866 | const file1 = try dir.createFile(filename, .{ .lock = .Exclusive }); |
| 867 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); | ||
| 868 | defer file1.close(); | 867 | defer file1.close(); |
| 869 | ctx.evt.set(); | 868 | evt.set(); |
| 870 | } | 869 | } |
| 871 | }; | 870 | }; |
| 872 | 871 | ||
| ... | @@ -874,8 +873,8 @@ test "open file with exclusive lock twice, make sure it waits" { | ... | @@ -874,8 +873,8 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 874 | try evt.init(); | 873 | try evt.init(); |
| 875 | defer evt.deinit(); | 874 | defer evt.deinit(); |
| 876 | 875 | ||
| 877 | const t = try std.Thread.spawn(S.checkFn, S.C{ .dir = &tmp.dir, .evt = &evt }); | 876 | const t = try std.Thread.spawn(.{}, S.checkFn, .{ &tmp.dir, &evt }); |
| 878 | defer t.wait(); | 877 | defer t.join(); |
| 879 | 878 | ||
| 880 | const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms; | 879 | const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms; |
| 881 | // Make sure we've slept enough. | 880 | // Make sure we've slept enough. |
lib/std/net/test.zig+5-5| ... | @@ -161,8 +161,8 @@ test "listen on a port, send bytes, receive bytes" { | ... | @@ -161,8 +161,8 @@ test "listen on a port, send bytes, receive bytes" { |
| 161 | } | 161 | } |
| 162 | }; | 162 | }; |
| 163 | 163 | ||
| 164 | const t = try std.Thread.spawn(S.clientFn, server.listen_address); | 164 | const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address}); |
| 165 | defer t.wait(); | 165 | defer t.join(); |
| 166 | 166 | ||
| 167 | var client = try server.accept(); | 167 | var client = try server.accept(); |
| 168 | defer client.stream.close(); | 168 | defer client.stream.close(); |
| ... | @@ -277,7 +277,7 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -277,7 +277,7 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 277 | try server.listen(socket_addr); | 277 | try server.listen(socket_addr); |
| 278 | 278 | ||
| 279 | const S = struct { | 279 | const S = struct { |
| 280 | fn clientFn(_: void) !void { | 280 | fn clientFn() !void { |
| 281 | const socket = try net.connectUnixSocket(socket_path); | 281 | const socket = try net.connectUnixSocket(socket_path); |
| 282 | defer socket.close(); | 282 | defer socket.close(); |
| 283 | 283 | ||
| ... | @@ -285,8 +285,8 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -285,8 +285,8 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 285 | } | 285 | } |
| 286 | }; | 286 | }; |
| 287 | 287 | ||
| 288 | const t = try std.Thread.spawn(S.clientFn, {}); | 288 | const t = try std.Thread.spawn(.{}, S.clientFn, .{}); |
| 289 | defer t.wait(); | 289 | defer t.join(); |
| 290 | 290 | ||
| 291 | var client = try server.accept(); | 291 | var client = try server.accept(); |
| 292 | defer client.stream.close(); | 292 | defer client.stream.close(); |
lib/std/once.zig+4-4| ... | @@ -55,16 +55,16 @@ test "Once executes its function just once" { | ... | @@ -55,16 +55,16 @@ test "Once executes its function just once" { |
| 55 | global_once.call(); | 55 | global_once.call(); |
| 56 | global_once.call(); | 56 | global_once.call(); |
| 57 | } else { | 57 | } else { |
| 58 | var threads: [10]*std.Thread = undefined; | 58 | var threads: [10]std.Thread = undefined; |
| 59 | defer for (threads) |handle| handle.wait(); | 59 | defer for (threads) |handle| handle.join(); |
| 60 | 60 | ||
| 61 | for (threads) |*handle| { | 61 | for (threads) |*handle| { |
| 62 | handle.* = try std.Thread.spawn(struct { | 62 | handle.* = try std.Thread.spawn(.{}, struct { |
| 63 | fn thread_fn(x: u8) void { | 63 | fn thread_fn(x: u8) void { |
| 64 | _ = x; | 64 | _ = x; |
| 65 | global_once.call(); | 65 | global_once.call(); |
| 66 | } | 66 | } |
| 67 | }.thread_fn, 0); | 67 | }.thread_fn, .{0}); |
| 68 | } | 68 | } |
| 69 | } | 69 | } |
| 70 | 70 |
lib/std/os/test.zig+19-30| ... | @@ -320,18 +320,9 @@ test "std.Thread.getCurrentId" { | ... | @@ -320,18 +320,9 @@ test "std.Thread.getCurrentId" { |
| 320 | if (builtin.single_threaded) return error.SkipZigTest; | 320 | if (builtin.single_threaded) return error.SkipZigTest; |
| 321 | 321 | ||
| 322 | var thread_current_id: Thread.Id = undefined; | 322 | var thread_current_id: Thread.Id = undefined; |
| 323 | const thread = try Thread.spawn(testThreadIdFn, &thread_current_id); | 323 | const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id}); |
| 324 | const thread_id = thread.handle(); | 324 | thread.join(); |
| 325 | thread.wait(); | 325 | try expect(Thread.getCurrentId() != thread_current_id); |
| 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 | } | ||
| 335 | } | 326 | } |
| 336 | 327 | ||
| 337 | test "spawn threads" { | 328 | test "spawn threads" { |
| ... | @@ -339,21 +330,20 @@ test "spawn threads" { | ... | @@ -339,21 +330,20 @@ test "spawn threads" { |
| 339 | 330 | ||
| 340 | var shared_ctx: i32 = 1; | 331 | var shared_ctx: i32 = 1; |
| 341 | 332 | ||
| 342 | const thread1 = try Thread.spawn(start1, {}); | 333 | const thread1 = try Thread.spawn(.{}, start1, .{}); |
| 343 | const thread2 = try Thread.spawn(start2, &shared_ctx); | 334 | const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx}); |
| 344 | const thread3 = try Thread.spawn(start2, &shared_ctx); | 335 | const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx}); |
| 345 | const thread4 = try Thread.spawn(start2, &shared_ctx); | 336 | const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx}); |
| 346 | 337 | ||
| 347 | thread1.wait(); | 338 | thread1.join(); |
| 348 | thread2.wait(); | 339 | thread2.join(); |
| 349 | thread3.wait(); | 340 | thread3.join(); |
| 350 | thread4.wait(); | 341 | thread4.join(); |
| 351 | 342 | ||
| 352 | try expect(shared_ctx == 4); | 343 | try expect(shared_ctx == 4); |
| 353 | } | 344 | } |
| 354 | 345 | ||
| 355 | fn start1(ctx: void) u8 { | 346 | fn start1() u8 { |
| 356 | _ = ctx; | ||
| 357 | return 0; | 347 | return 0; |
| 358 | } | 348 | } |
| 359 | 349 | ||
| ... | @@ -365,22 +355,21 @@ fn start2(ctx: *i32) u8 { | ... | @@ -365,22 +355,21 @@ fn start2(ctx: *i32) u8 { |
| 365 | test "cpu count" { | 355 | test "cpu count" { |
| 366 | if (native_os == .wasi) return error.SkipZigTest; | 356 | if (native_os == .wasi) return error.SkipZigTest; |
| 367 | 357 | ||
| 368 | const cpu_count = try Thread.cpuCount(); | 358 | const cpu_count = try Thread.getCpuCount(); |
| 369 | try expect(cpu_count >= 1); | 359 | try expect(cpu_count >= 1); |
| 370 | } | 360 | } |
| 371 | 361 | ||
| 372 | test "thread local storage" { | 362 | test "thread local storage" { |
| 373 | if (builtin.single_threaded) return error.SkipZigTest; | 363 | if (builtin.single_threaded) return error.SkipZigTest; |
| 374 | const thread1 = try Thread.spawn(testTls, {}); | 364 | const thread1 = try Thread.spawn(.{}, testTls, .{}); |
| 375 | const thread2 = try Thread.spawn(testTls, {}); | 365 | const thread2 = try Thread.spawn(.{}, testTls, .{}); |
| 376 | try testTls({}); | 366 | try testTls(); |
| 377 | thread1.wait(); | 367 | thread1.join(); |
| 378 | thread2.wait(); | 368 | thread2.join(); |
| 379 | } | 369 | } |
| 380 | 370 | ||
| 381 | threadlocal var x: i32 = 1234; | 371 | threadlocal var x: i32 = 1234; |
| 382 | fn testTls(context: void) !void { | 372 | fn testTls() !void { |
| 383 | _ = context; | ||
| 384 | if (x != 1234) return error.TlsBadStartValue; | 373 | if (x != 1234) return error.TlsBadStartValue; |
| 385 | x += 1; | 374 | x += 1; |
| 386 | if (x != 1235) return error.TlsBadEndValue; | 375 | if (x != 1235) return error.TlsBadEndValue; |
lib/std/target.zig+19-4| ... | @@ -69,6 +69,13 @@ pub const Target = struct { | ... | @@ -69,6 +69,13 @@ pub const Target = struct { |
| 69 | }; | 69 | }; |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | pub fn isBSD(tag: Tag) bool { | ||
| 73 | return tag.isDarwin() or switch (tag) { | ||
| 74 | .kfreebsd, .freebsd, .openbsd, .netbsd, .dragonfly => true, | ||
| 75 | else => false, | ||
| 76 | }; | ||
| 77 | } | ||
| 78 | |||
| 72 | pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 { | 79 | pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 { |
| 73 | if (tag.isDarwin()) { | 80 | if (tag.isDarwin()) { |
| 74 | return ".dylib"; | 81 | return ".dylib"; |
| ... | @@ -787,6 +794,13 @@ pub const Target = struct { | ... | @@ -787,6 +794,13 @@ pub const Target = struct { |
| 787 | }; | 794 | }; |
| 788 | } | 795 | } |
| 789 | 796 | ||
| 797 | pub fn isAARCH64(arch: Arch) bool { | ||
| 798 | return switch (arch) { | ||
| 799 | .aarch64, .aarch64_be, .aarch64_32 => true, | ||
| 800 | else => false, | ||
| 801 | }; | ||
| 802 | } | ||
| 803 | |||
| 790 | pub fn isThumb(arch: Arch) bool { | 804 | pub fn isThumb(arch: Arch) bool { |
| 791 | return switch (arch) { | 805 | return switch (arch) { |
| 792 | .thumb, .thumbeb => true, | 806 | .thumb, .thumbeb => true, |
| ... | @@ -1365,10 +1379,7 @@ pub const Target = struct { | ... | @@ -1365,10 +1379,7 @@ pub const Target = struct { |
| 1365 | } | 1379 | } |
| 1366 | 1380 | ||
| 1367 | pub fn isAndroid(self: Target) bool { | 1381 | pub fn isAndroid(self: Target) bool { |
| 1368 | return switch (self.abi) { | 1382 | return self.abi == .android; |
| 1369 | .android => true, | ||
| 1370 | else => false, | ||
| 1371 | }; | ||
| 1372 | } | 1383 | } |
| 1373 | 1384 | ||
| 1374 | pub fn isWasm(self: Target) bool { | 1385 | pub fn isWasm(self: Target) bool { |
| ... | @@ -1379,6 +1390,10 @@ pub const Target = struct { | ... | @@ -1379,6 +1390,10 @@ pub const Target = struct { |
| 1379 | return self.os.tag.isDarwin(); | 1390 | return self.os.tag.isDarwin(); |
| 1380 | } | 1391 | } |
| 1381 | 1392 | ||
| 1393 | pub fn isBSD(self: Target) bool { | ||
| 1394 | return self.os.tag.isBSD(); | ||
| 1395 | } | ||
| 1396 | |||
| 1382 | pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool { | 1397 | pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool { |
| 1383 | return os_tag == .linux and abi.isGnu(); | 1398 | return os_tag == .linux and abi.isGnu(); |
| 1384 | } | 1399 | } |
src/ThreadPool.zig+4-4| ... | @@ -21,7 +21,7 @@ const Runnable = struct { | ... | @@ -21,7 +21,7 @@ const Runnable = struct { |
| 21 | 21 | ||
| 22 | const Worker = struct { | 22 | const Worker = struct { |
| 23 | pool: *ThreadPool, | 23 | pool: *ThreadPool, |
| 24 | thread: *std.Thread, | 24 | thread: std.Thread, |
| 25 | /// The node is for this worker only and must have an already initialized event | 25 | /// The node is for this worker only and must have an already initialized event |
| 26 | /// when the thread is spawned. | 26 | /// when the thread is spawned. |
| 27 | idle_node: IdleQueue.Node, | 27 | idle_node: IdleQueue.Node, |
| ... | @@ -60,7 +60,7 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { | ... | @@ -60,7 +60,7 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { |
| 60 | if (std.builtin.single_threaded) | 60 | if (std.builtin.single_threaded) |
| 61 | return; | 61 | return; |
| 62 | 62 | ||
| 63 | const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1); | 63 | const worker_count = std.math.max(1, std.Thread.getCpuCount() catch 1); |
| 64 | self.workers = try allocator.alloc(Worker, worker_count); | 64 | self.workers = try allocator.alloc(Worker, worker_count); |
| 65 | errdefer allocator.free(self.workers); | 65 | errdefer allocator.free(self.workers); |
| 66 | 66 | ||
| ... | @@ -74,13 +74,13 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { | ... | @@ -74,13 +74,13 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { |
| 74 | try worker.idle_node.data.init(); | 74 | try worker.idle_node.data.init(); |
| 75 | errdefer worker.idle_node.data.deinit(); | 75 | errdefer worker.idle_node.data.deinit(); |
| 76 | 76 | ||
| 77 | worker.thread = try std.Thread.spawn(Worker.run, worker); | 77 | worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker}); |
| 78 | } | 78 | } |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | fn destroyWorkers(self: *ThreadPool, spawned: usize) void { | 81 | fn destroyWorkers(self: *ThreadPool, spawned: usize) void { |
| 82 | for (self.workers[0..spawned]) |*worker| { | 82 | for (self.workers[0..spawned]) |*worker| { |
| 83 | worker.thread.wait(); | 83 | worker.thread.join(); |
| 84 | worker.idle_node.data.deinit(); | 84 | worker.idle_node.data.deinit(); |
| 85 | } | 85 | } |
| 86 | } | 86 | } |
tools/update_cpu_features.zig+10-8| ... | @@ -816,18 +816,20 @@ pub fn main() anyerror!void { | ... | @@ -816,18 +816,20 @@ pub fn main() anyerror!void { |
| 816 | }); | 816 | }); |
| 817 | } | 817 | } |
| 818 | } else { | 818 | } else { |
| 819 | var threads = try arena.alloc(*std.Thread, llvm_targets.len); | 819 | var threads = try arena.alloc(std.Thread, llvm_targets.len); |
| 820 | for (llvm_targets) |llvm_target, i| { | 820 | for (llvm_targets) |llvm_target, i| { |
| 821 | threads[i] = try std.Thread.spawn(processOneTarget, .{ | 821 | threads[i] = try std.Thread.spawn(.{}, processOneTarget, .{ |
| 822 | .llvm_tblgen_exe = llvm_tblgen_exe, | 822 | Job{ |
| 823 | .llvm_src_root = llvm_src_root, | 823 | .llvm_tblgen_exe = llvm_tblgen_exe, |
| 824 | .zig_src_dir = zig_src_dir, | 824 | .llvm_src_root = llvm_src_root, |
| 825 | .root_progress = root_progress, | 825 | .zig_src_dir = zig_src_dir, |
| 826 | .llvm_target = llvm_target, | 826 | .root_progress = root_progress, |
| 827 | .llvm_target = llvm_target, | ||
| 828 | }, | ||
| 827 | }); | 829 | }); |
| 828 | } | 830 | } |
| 829 | for (threads) |thread| { | 831 | for (threads) |thread| { |
| 830 | thread.wait(); | 832 | thread.join(); |
| 831 | } | 833 | } |
| 832 | } | 834 | } |
| 833 | } | 835 | } |