zig/std/thread.zig

346 lines
14 KiB
Zig
Raw Normal View History

const builtin = @import("builtin");
const std = @import("std.zig");
2019-05-26 01:07:44 +08:00
const os = std.os;
2019-05-27 11:35:26 +08:00
const mem = std.mem;
const windows = std.os.windows;
2019-05-26 01:07:44 +08:00
const c = std.c;
2019-05-27 11:35:26 +08:00
const assert = std.debug.assert;
pub const Thread = struct {
data: Data,
pub const use_pthreads = !windows.is_the_target and builtin.link_libc;
/// Represents a kernel thread handle.
/// May be an integer or a pointer depending on the platform.
/// On Linux and POSIX, this is the same as Id.
pub const Handle = if (use_pthreads)
c.pthread_t
else switch (builtin.os) {
2019-05-26 01:07:44 +08:00
.linux => i32,
.windows => windows.HANDLE,
else => @compileError("Unsupported OS"),
};
/// Represents a unique ID per thread.
/// May be an integer or pointer depending on the platform.
/// On Linux and POSIX, this is the same as Handle.
pub const Id = switch (builtin.os) {
2019-05-26 01:07:44 +08:00
.windows => windows.DWORD,
else => Handle,
};
pub const Data = if (use_pthreads)
struct {
handle: Thread.Handle,
2019-05-27 11:35:26 +08:00
memory: []align(mem.page_size) u8,
}
else switch (builtin.os) {
2019-05-26 01:07:44 +08:00
.linux => struct {
handle: Thread.Handle,
2019-05-27 11:35:26 +08:00
memory: []align(mem.page_size) u8,
},
2019-05-26 01:07:44 +08:00
.windows => struct {
handle: Thread.Handle,
alloc_start: *c_void,
heap_handle: windows.HANDLE,
},
else => @compileError("Unsupported OS"),
};
/// Returns the ID of the calling thread.
/// Makes a syscall every time the function is called.
/// On Linux and POSIX, this Id is the same as a Handle.
pub fn getCurrentId() Id {
if (use_pthreads) {
return c.pthread_self();
} else
return switch (builtin.os) {
2019-05-27 11:35:26 +08:00
.linux => os.linux.gettid(),
2019-05-27 13:35:58 +08:00
.windows => windows.kernel32.GetCurrentThreadId(),
else => @compileError("Unsupported OS"),
};
}
/// Returns the handle of this thread.
/// On Linux and POSIX, this is the same as Id.
/// On Linux, it is possible that the thread spawned with `spawn`
/// finishes executing entirely before the clone syscall completes. In this
/// case, this function will return 0 rather than the no-longer-existing thread's
/// pid.
pub fn handle(self: Thread) Handle {
return self.data.handle;
}
pub fn wait(self: *const Thread) void {
if (use_pthreads) {
const err = c.pthread_join(self.data.handle, null);
switch (err) {
0 => {},
2019-05-26 01:07:44 +08:00
os.EINVAL => unreachable,
os.ESRCH => unreachable,
os.EDEADLK => unreachable,
else => unreachable,
}
2019-05-27 11:35:26 +08:00
os.munmap(self.data.memory);
} else switch (builtin.os) {
2019-05-26 01:07:44 +08:00
.linux => {
while (true) {
const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
if (pid_value == 0) break;
2019-05-27 11:35:26 +08:00
const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
switch (os.linux.getErrno(rc)) {
0 => continue,
2019-05-26 01:07:44 +08:00
os.EINTR => continue,
os.EAGAIN => continue,
else => unreachable,
}
}
2019-05-27 11:35:26 +08:00
os.munmap(self.data.memory);
},
2019-05-26 01:07:44 +08:00
.windows => {
2019-05-27 13:35:58 +08:00
windows.WaitForSingleObject(self.data.handle, windows.INFINITE) catch unreachable;
windows.CloseHandle(self.data.handle);
windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
},
else => @compileError("Unsupported OS"),
}
}
pub const SpawnError = error{
/// A system-imposed limit on the number of threads was encountered.
/// There are a number of limits that may trigger this error:
/// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
/// which limits the number of processes and threads for a real
/// user ID, was reached;
/// * the kernel's system-wide limit on the number of processes and
/// threads, /proc/sys/kernel/threads-max, was reached (see
/// proc(5));
/// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
/// reached (see proc(5)); or
/// * the PID limit (pids.max) imposed by the cgroup "process num
/// ber" (PIDs) controller was reached.
ThreadQuotaExceeded,
/// The kernel cannot allocate sufficient memory to allocate a task structure
/// for the child, or to copy those parts of the caller's context that need to
/// be copied.
SystemResources,
/// Not enough userland memory to spawn the thread.
OutOfMemory,
2019-05-27 11:35:26 +08:00
/// `mlockall` is enabled, and the memory needed to spawn the thread
/// would exceed the limit.
LockedMemoryLimitExceeded,
Unexpected,
};
/// caller must call wait on the returned thread
/// fn startFn(@typeOf(context)) T
/// where T is u8, noreturn, void, or !void
/// caller must call wait on the returned thread
pub fn spawn(context: var, comptime startFn: var) SpawnError!*Thread {
if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
// TODO compile-time call graph analysis to determine stack upper bound
// https://github.com/ziglang/zig/issues/157
const default_stack_size = 8 * 1024 * 1024;
const Context = @typeOf(context);
comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
if (builtin.os == builtin.Os.windows) {
const WinThread = struct {
const OuterContext = struct {
thread: Thread,
inner: Context,
};
extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD {
const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
switch (@typeId(@typeOf(startFn).ReturnType)) {
2019-05-26 01:07:44 +08:00
.Int => {
return startFn(arg);
},
2019-05-26 01:07:44 +08:00
.Void => {
startFn(arg);
return 0;
},
else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
}
}
};
2019-05-27 13:35:58 +08:00
const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
2019-05-27 13:35:58 +08:00
const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);
const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
outer_context.* = WinThread.OuterContext{
.thread = Thread{
.data = Thread.Data{
.heap_handle = heap_handle,
.alloc_start = bytes_ptr,
.handle = undefined,
},
},
.inner = context,
};
const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
2019-05-27 13:35:58 +08:00
outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
switch (windows.kernel32.GetLastError()) {
else => |err| return windows.unexpectedError(err),
}
};
return &outer_context.thread;
}
const MainFuncs = struct {
extern fn linuxThreadMain(ctx_addr: usize) u8 {
const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
switch (@typeId(@typeOf(startFn).ReturnType)) {
2019-05-26 01:07:44 +08:00
.Int => {
return startFn(arg);
},
2019-05-26 01:07:44 +08:00
.Void => {
startFn(arg);
return 0;
},
else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
}
}
extern fn posixThreadMain(ctx: ?*c_void) ?*c_void {
if (@sizeOf(Context) == 0) {
_ = startFn({});
return null;
} else {
_ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*);
return null;
}
}
};
2019-05-27 11:35:26 +08:00
const MAP_GROWSDOWN = if (os.linux.is_the_target) os.linux.MAP_GROWSDOWN else 0;
var stack_end_offset: usize = undefined;
var thread_start_offset: usize = undefined;
var context_start_offset: usize = undefined;
var tls_start_offset: usize = undefined;
const mmap_len = blk: {
// First in memory will be the stack, which grows downwards.
2019-05-27 01:17:34 +08:00
var l: usize = mem.alignForward(default_stack_size, mem.page_size);
stack_end_offset = l;
// Above the stack, so that it can be in the same mmap call, put the Thread object.
l = mem.alignForward(l, @alignOf(Thread));
thread_start_offset = l;
l += @sizeOf(Thread);
// Next, the Context object.
if (@sizeOf(Context) != 0) {
l = mem.alignForward(l, @alignOf(Context));
context_start_offset = l;
l += @sizeOf(Context);
}
// Finally, the Thread Local Storage, if any.
if (!Thread.use_pthreads) {
2019-05-27 11:35:26 +08:00
if (os.linux.tls.tls_image) |tls_img| {
l = mem.alignForward(l, @alignOf(usize));
tls_start_offset = l;
l += tls_img.alloc_size;
}
}
break :blk l;
};
2019-05-27 11:35:26 +08:00
const mmap_slice = os.mmap(
null,
mem.alignForward(mmap_len, mem.page_size),
os.PROT_READ | os.PROT_WRITE,
os.MAP_PRIVATE | os.MAP_ANONYMOUS | MAP_GROWSDOWN,
-1,
0,
) catch |err| switch (err) {
error.MemoryMappingNotSupported => unreachable, // no file descriptor
error.AccessDenied => unreachable, // no file descriptor
error.PermissionDenied => unreachable, // no file descriptor
else => |e| return e,
};
errdefer os.munmap(mmap_slice);
const mmap_addr = @ptrToInt(mmap_slice.ptr);
const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
2019-05-27 11:35:26 +08:00
thread_ptr.data.memory = mmap_slice;
var arg: usize = undefined;
if (@sizeOf(Context) != 0) {
arg = mmap_addr + context_start_offset;
const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
context_ptr.* = context;
}
if (Thread.use_pthreads) {
// use pthreads
var attr: c.pthread_attr_t = undefined;
if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
defer assert(c.pthread_attr_destroy(&attr) == 0);
2019-05-27 11:35:26 +08:00
assert(c.pthread_attr_setstack(&attr, mmap_slice.ptr, stack_end_offset) == 0);
const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg));
switch (err) {
0 => return thread_ptr,
2019-05-26 01:07:44 +08:00
os.EAGAIN => return error.SystemResources,
os.EPERM => unreachable,
os.EINVAL => unreachable,
else => return os.unexpectedErrno(@intCast(usize, err)),
}
2019-05-27 11:35:26 +08:00
} else if (os.linux.is_the_target) {
2019-05-26 01:07:44 +08:00
var flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | os.CLONE_SIGHAND |
os.CLONE_THREAD | os.CLONE_SYSVSEM | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
os.CLONE_DETACHED;
var newtls: usize = undefined;
2019-05-27 11:35:26 +08:00
if (os.linux.tls.tls_image) |tls_img| {
newtls = os.linux.tls.copyTLS(mmap_addr + tls_start_offset);
2019-05-26 01:07:44 +08:00
flags |= os.CLONE_SETTLS;
}
2019-05-26 01:07:44 +08:00
const rc = os.linux.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
switch (os.errno(rc)) {
0 => return thread_ptr,
2019-05-26 01:07:44 +08:00
os.EAGAIN => return error.ThreadQuotaExceeded,
os.EINVAL => unreachable,
os.ENOMEM => return error.SystemResources,
os.ENOSPC => unreachable,
os.EPERM => unreachable,
os.EUSERS => unreachable,
else => |err| return os.unexpectedErrno(err),
}
} else {
@compileError("Unsupported OS");
}
}
pub const CpuCountError = error{
OutOfMemory,
PermissionDenied,
Unexpected,
};
2019-05-26 01:07:44 +08:00
pub fn cpuCount() CpuCountError!usize {
if (os.linux.is_the_target) {
const cpu_set = try os.sched_getaffinity(0);
2019-05-27 11:35:26 +08:00
return usize(os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
2019-05-26 01:07:44 +08:00
}
if (os.windows.is_the_target) {
var system_info: windows.SYSTEM_INFO = undefined;
2019-05-27 13:35:58 +08:00
windows.kernel32.GetSystemInfo(&system_info);
2019-05-26 01:07:44 +08:00
return @intCast(usize, system_info.dwNumberOfProcessors);
}
2019-05-26 01:07:44 +08:00
var count: c_int = undefined;
var count_len: usize = @sizeOf(c_int);
const name = if (os.darwin.is_the_target) c"hw.logicalcpu" else c"hw.ncpu";
try os.sysctlbyname(name, @ptrCast(*c_void, &count), &count_len, null, 0);
return @intCast(usize, count);
}
};