feature. See also
. The project being documented here (as the example) is the Zig library itself.
Thread.WindowsThreadImpl
const WindowsThreadImpl = struct
File
Code
const WindowsThreadImpl = struct {
pub const ThreadHandle = windows.HANDLE;
fn getCurrentId() windows.DWORD {
return windows.GetCurrentThreadId();
}
fn getCpuCount() !usize {
return windows.peb().NumberOfProcessors;
}
thread: *ThreadCompletion,
const ThreadCompletion = struct {
completion: Completion,
heap_ptr: windows.PVOID,
heap_handle: *windows.HEAP,
thread_handle: windows.HANDLE = undefined,
fn free(self: ThreadCompletion) void {
const status = windows.ntdll.RtlFreeHeap(self.heap_handle, .{}, self.heap_ptr);
assert(status != 0);
}
};
fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
const Args = @TypeOf(args);
const Instance = struct {
fn_args: Args,
thread: ThreadCompletion,
fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.NTSTATUS {
const self: *@This() = @ptrCast(@alignCast(raw_ptr));
defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
.running => {},
.completed => unreachable,
.detached => self.thread.free(),
};
return callFn(f, self.fn_args);
}
};
const heap_handle = windows.GetProcessHeap() orelse return error.OutOfMemory;
const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
const alloc_ptr = windows.ntdll.RtlAllocateHeap(heap_handle, .{}, alloc_bytes) orelse return error.OutOfMemory;
errdefer assert(windows.ntdll.RtlFreeHeap(heap_handle, .{}, alloc_ptr) != 0);
const instance_bytes = @as([*]u8, @ptrCast(alloc_ptr))[0..alloc_bytes];
var fba = std.heap.FixedBufferAllocator.init(instance_bytes);
const instance = fba.allocator().create(Instance) catch unreachable;
instance.* = .{
.fn_args = args,
.thread = .{
.completion = Completion.init(.running),
.heap_ptr = alloc_ptr,
.heap_handle = heap_handle,
},
};
// minimum stack size. Going lower makes it default to that specified in the executable
// (~1mb). Its also fine if the limit here is incorrect as stack size is only a hint.
const stack_size = @max(64 * 1024, std.math.lossyCast(u32, config.stack_size));
// However, CreateThread is just a wrapper around CreateRemoteThreadEx,
// so that's the more relevant function in this context.
//
// https://github.com/wine-mirror/wine/blob/3d128be6400b3869119d293d0c8fa9e7702978f8/dlls/kernelbase/thread.c#L85
instance.thread.thread_handle = blk: {
var active_ctx: ?windows.HANDLE = undefined;
switch (windows.ntdll.RtlGetActiveActivationContext(&active_ctx)) {
.SUCCESS => {},
else => |status| return windows.unexpectedStatus(status),
}
defer if (active_ctx) |ctx| windows.ntdll.RtlReleaseActivationContext(ctx);
var teb: *windows.TEB = undefined;
var attr_list = windows.PS.ATTRIBUTE.LIST{
.TotalLength = @sizeOf(windows.PS.ATTRIBUTE.LIST),
.Attributes = .{
.{
.Attribute = .TEB_ADDRESS,
.Size = @sizeOf(*windows.TEB),
.u = .{
.ValuePtr = @ptrCast(&teb),
},
.ReturnLength = null,
},
},
};
var thread_handle: windows.HANDLE = undefined;
switch (windows.ntdll.NtCreateThreadEx(
&thread_handle,
.{ .MAXIMUM_ALLOWED = true },
&.{},
windows.GetCurrentProcess(),
Instance.entryFn,
instance,
.{ .CREATE_SUSPENDED = true },
0,
@fromBackingInt(@intCast(stack_size)),
.default,
&attr_list,
)) {
.SUCCESS => {},
else => |status| return windows.unexpectedStatus(status),
}
if (active_ctx) |ctx| {
var cookie: windows.ULONG = 0;
switch (windows.ntdll.RtlActivateActivationContextEx(0, teb, ctx, &cookie)) {
.SUCCESS => {},
else => |status| return windows.unexpectedStatus(status),
}
}
switch (windows.ntdll.NtResumeThread(thread_handle, null)) {
.SUCCESS => {},
else => |status| return windows.unexpectedStatus(status),
}
break :blk thread_handle;
};
return Impl{ .thread = &instance.thread };
}
fn getHandle(self: Impl) ThreadHandle {
return self.thread.thread_handle;
}
fn detach(self: Impl) void {
windows.CloseHandle(self.thread.thread_handle);
switch (self.thread.completion.swap(.detached, .seq_cst)) {
.running => {},
.completed => self.thread.free(),
.detached => unreachable,
}
}
fn join(self: Impl) void {
const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, .FALSE, &infinite_timeout)) {
windows.NTSTATUS.WAIT_0 => {},
else => |status| windows.unexpectedStatus(status) catch unreachable,
}
windows.CloseHandle(self.thread.thread_handle);
assert(self.thread.completion.load(.seq_cst) == .completed);
self.thread.free();
}
}