tiny.sys.thread
Defined in tiny.sys.
API (45)
Actions
Public operations.
Condition.broadcastCondition.signalCondition.waitJoinHandle.detachJoinHandle.joinJoinHandle.setNameMutex.lockMutex.tryLockMutex.unlockSemaphore.postSemaphore.waitblockUntilDifferentcpuCountcreateThreadSpecificKeycurrentAffinitycurrentIdcurrentPlacementgetThreadSpecificValueinitProcessThreadedIoinitThreadedIosetCurrentAffinitysetThreadSpecificValuespawnthreadSpecificDestructorPolicythreadSpecificDestructorsSupportedthreadsSupportedwakeAllyield
Types and contracts
Public types and contracts.
AffinityErrorConditionCpuSetIdJoinHandleMutexPlacementPlacementErrorSemaphoreSpawnErrorThreadSpecificDestructorThreadSpecificDestructorPolicyThreadSpecificErrorThreadSpecificKeyThreadedIoThreadedIoOptions
Values and defaults
Public values and defaults.
Source
Source: lib/sys/src/root.zig:52
zig
pub const thread = @import("thread.zig");Source: lib/sys/src/thread.zig
zig
const std = @import("std");const builtin = @import("builtin");const sys_allocator = @import("allocator.zig");const capabilities = @import("capabilities.zig");const linux = std.os.linux;const native_os = builtin.os.tag;const posix = std.posix;pub const required_capabilities = capabilities.host(&.{.threads});pub const ThreadSpecificError = error{ UnsupportedPlatform, SystemResources,};pub const SpawnError = error{ UnsupportedPlatform, OutOfMemory, SystemResources,};pub const Placement = struct { cpu_id: usize, numa_node: usize,};pub const PlacementError = error{ UnsupportedPlatform, QueryFailed };pub const CpuSet = linux.cpu_set_t;pub const AffinityError = error{ UnsupportedPlatform, QueryFailed, ApplyFailed };pub const ThreadSpecificDestructor = *const fn (value: *anyopaque) callconv(.c) void;pub const Id = std.Thread.Id;pub const ThreadedIo = std.Io.Threaded;pub const ThreadedIoOptions = std.Io.Threaded.InitOptions;pub const ThreadSpecificDestructorPolicy = enum { unsupported, native_spawn_exit,};fn threadedIo() std.Io { return std.Io.Threaded.global_single_threaded.io();}pub fn initThreadedIo(allocator: std.mem.Allocator, options: ThreadedIoOptions) ThreadedIo { return ThreadedIo.init(allocator, options);}pub fn initProcessThreadedIo(allocator: std.mem.Allocator) ThreadedIo { return ThreadedIo.init(allocator, .{ .environ = @import("env.zig").processEnviron(), });}pub const Mutex = struct { raw: std.Io.Mutex = .init, pub fn tryLock(self: *Mutex) bool { return self.raw.tryLock(); } pub fn lock(self: *Mutex) void { std.Io.Threaded.mutexLock(&self.raw); } pub fn unlock(self: *Mutex) void { std.Io.Threaded.mutexUnlock(&self.raw); }};pub const Condition = struct { raw: std.Io.Condition = .init, pub fn wait(self: *Condition, mutex: *Mutex) void { self.raw.waitUncancelable(threadedIo(), &mutex.raw); } pub fn signal(self: *Condition) void { self.raw.signal(threadedIo()); } pub fn broadcast(self: *Condition) void { self.raw.broadcast(threadedIo()); }};pub const Semaphore = struct { raw: std.Io.Semaphore = .{}, pub fn post(self: *Semaphore) void { self.raw.post(threadedIo()); } pub fn wait(self: *Semaphore) void { self.raw.waitUncancelable(threadedIo()); }};pub const ThreadSpecificKey = struct { index: usize = invalid_thread_specific_key,};const invalid_thread_specific_key = std.math.maxInt(usize);const native_thread_specific_destructor_iterations = 4;const NativeThreadSpecificRegistry = struct { mutex: std.atomic.Mutex = .unlocked, destructors: std.ArrayList(ThreadSpecificDestructor) = .empty, fn createKey(self: *NativeThreadSpecificRegistry, allocator: std.mem.Allocator, callback: ThreadSpecificDestructor) ThreadSpecificError!ThreadSpecificKey { self.lock(); defer self.mutex.unlock(); const index = self.destructors.items.len; if (index == invalid_thread_specific_key) return error.SystemResources; self.destructors.append(allocator, callback) catch return error.SystemResources; return .{ .index = index }; } fn destructor(self: *NativeThreadSpecificRegistry, key_index: usize) ?ThreadSpecificDestructor { self.lock(); defer self.mutex.unlock(); if (key_index >= self.destructors.items.len) return null; return self.destructors.items[key_index]; } fn lock(self: *NativeThreadSpecificRegistry) void { while (!self.mutex.tryLock()) std.atomic.spinLoopHint(); }};const NativeThreadSpecificValues = struct { values: std.ArrayList(?*anyopaque) = .empty, fn set(self: *NativeThreadSpecificValues, allocator: std.mem.Allocator, key: ThreadSpecificKey, value: ?*anyopaque) bool { if (key.index == invalid_thread_specific_key) return false; if (value == null and key.index >= self.values.items.len) return true; while (self.values.items.len <= key.index) { self.values.append(allocator, null) catch return false; } self.values.items[key.index] = value; return true; } fn get(self: *NativeThreadSpecificValues, key: ThreadSpecificKey) ?*anyopaque { if (key.index == invalid_thread_specific_key or key.index >= self.values.items.len) return null; return self.values.items[key.index]; } fn runDestructors(self: *NativeThreadSpecificValues, allocator: std.mem.Allocator) void { defer { self.values.deinit(allocator); self.* = .{}; } var iteration: usize = 0; while (iteration < native_thread_specific_destructor_iterations) : (iteration += 1) { var called = false; var index: usize = 0; while (index < self.values.items.len) : (index += 1) { const value = self.values.items[index] orelse continue; const destructor = native_thread_specific_registry.destructor(index) orelse continue; self.values.items[index] = null; called = true; destructor(value); } if (!called) break; } }};var native_thread_specific_registry: NativeThreadSpecificRegistry = .{};threadlocal var native_thread_specific_values: NativeThreadSpecificValues = .{};pub const JoinHandle = struct { raw: std.Thread, pub fn join(self: JoinHandle) void { self.raw.join(); } pub fn detach(self: JoinHandle) void { self.raw.detach(); } pub fn setName( self: JoinHandle, name: []const u8, ) std.Thread.SetNameError!void { try self.raw.setName(std.Options.debug_io, name); }};pub fn blockUntilDifferent( previous: u32, current: *const std.atomic.Value(u32),) u32 { while (true) { const next = current.load(.acquire); if (next != previous) return next; std.Io.futexWaitUncancelable(threadedIo(), u32, ¤t.raw, previous); }}pub fn wakeAll(current: *std.atomic.Value(u32)) void { std.Io.futexWake(threadedIo(), u32, ¤t.raw, std.math.maxInt(u32));}pub fn threadsSupported() bool { return comptime capabilities.current.supportsThreads();}pub fn spawn(comptime func: anytype, args: anytype) SpawnError!JoinHandle { if (comptime !threadsSupported()) return error.UnsupportedPlatform; const Runner = struct { fn run(captured_args: @TypeOf(args)) void { defer runNativeThreadSpecificDestructors(); _ = @call(.auto, func, captured_args); } }; return .{ .raw = std.Thread.spawn(.{}, Runner.run, .{args}) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.SystemResources, } };}pub fn yield() void { if (comptime !threadsSupported()) return; std.Thread.yield() catch {};}pub fn cpuCount() usize { if (comptime !threadsSupported()) return 1; return std.Thread.getCpuCount() catch 1;}pub fn currentId() Id { if (comptime !threadsSupported()) return 0; return std.Thread.getCurrentId();}pub fn currentPlacement() PlacementError!Placement { if (comptime native_os != .linux) return error.UnsupportedPlatform; var cpu_id: usize = 0; var numa_node: usize = 0; return switch (linux.errno(linux.getcpu(&cpu_id, &numa_node))) { .SUCCESS => .{ .cpu_id = cpu_id, .numa_node = numa_node }, .NOSYS => error.UnsupportedPlatform, else => error.QueryFailed, };}pub fn currentAffinity() AffinityError!CpuSet { if (comptime native_os != .linux) return error.UnsupportedPlatform; return posix.sched_getaffinity(0) catch return error.QueryFailed;}pub fn setCurrentAffinity(set: *const CpuSet) AffinityError!void { if (comptime native_os != .linux) return error.UnsupportedPlatform; linux.sched_setaffinity(0, set) catch return error.ApplyFailed;}pub fn threadSpecificDestructorPolicy() ThreadSpecificDestructorPolicy { if (comptime threadsSupported()) return .native_spawn_exit; return .unsupported;}pub fn threadSpecificDestructorsSupported() bool { return threadSpecificDestructorPolicy() != .unsupported;}pub fn createThreadSpecificKey(destructor: ThreadSpecificDestructor) ThreadSpecificError!ThreadSpecificKey { if (comptime !threadSpecificDestructorsSupported()) return error.UnsupportedPlatform; return native_thread_specific_registry.createKey(sys_allocator.processAllocator(), destructor);}pub fn setThreadSpecificValue(key: ThreadSpecificKey, value: ?*anyopaque) bool { if (comptime !threadSpecificDestructorsSupported()) return false; return native_thread_specific_values.set(sys_allocator.processAllocator(), key, value);}pub fn getThreadSpecificValue(key: ThreadSpecificKey) ?*anyopaque { if (comptime !threadSpecificDestructorsSupported()) return null; return native_thread_specific_values.get(key);}fn runNativeThreadSpecificDestructors() void { if (comptime !threadSpecificDestructorsSupported()) return; native_thread_specific_values.runDestructors(sys_allocator.processAllocator());}test "thread-specific destructor support uses sys-owned thread exit policy" { const expected: ThreadSpecificDestructorPolicy = if (comptime threadsSupported()) .native_spawn_exit else .unsupported; try std.testing.expectEqual(expected, threadSpecificDestructorPolicy()); try std.testing.expectEqual(expected != .unsupported, threadSpecificDestructorsSupported());}test "thread support follows host capabilities" { try std.testing.expectEqual(capabilities.current.supportsThreads(), threadsSupported()); try std.testing.expect(cpuCount() >= 1); try std.testing.expectEqual(currentId(), currentId());}test "current placement translates the host query" { if (native_os != .linux) { try std.testing.expectError(error.UnsupportedPlatform, currentPlacement()); return; } const placement = try currentPlacement(); try std.testing.expect(placement.cpu_id < std.math.maxInt(usize)); try std.testing.expect(placement.numa_node < std.math.maxInt(usize)); _ = try currentAffinity();}test "semaphore routes through sys thread io policy" { var semaphore: Semaphore = .{}; semaphore.post(); semaphore.wait();}const FutexProbe = struct { current: *std.atomic.Value(u32), ready: *std.atomic.Value(bool), observed: *u32, fn wait(self: FutexProbe) void { self.ready.store(true, .release); self.observed.* = blockUntilDifferent(7, self.current); }};test "futex boundary waits on and wakes an atomic word" { if (!threadsSupported()) return error.SkipZigTest; var current = std.atomic.Value(u32).init(7); var ready = std.atomic.Value(bool).init(false); var observed: u32 = 0; const handle = try spawn(FutexProbe.wait, .{ FutexProbe{ .current = ¤t, .ready = &ready, .observed = &observed, }, }); while (!ready.load(.acquire)) std.atomic.spinLoopHint(); current.store(11, .release); wakeAll(¤t); handle.join(); try std.testing.expectEqual(@as(u32, 11), observed);}const ThreadSpecificProbe = struct { fn destroy(value: *anyopaque) callconv(.c) void { const counter: *std.atomic.Value(usize) = @ptrCast(@alignCast(value)); _ = counter.fetchAdd(1, .monotonic); } fn setValue(key: ThreadSpecificKey, counter: *std.atomic.Value(usize)) void { if (!setThreadSpecificValue(key, counter)) @panic("thread specific set failed"); } fn isolate(key: ThreadSpecificKey, expected_missing: *bool, value: *usize, result: *usize) void { expected_missing.* = getThreadSpecificValue(key) == null; if (!setThreadSpecificValue(key, value)) @panic("thread specific set failed"); result.* = @intFromPtr(getThreadSpecificValue(key) orelse return); }};test "native thread-specific destructor runs when sys-spawned thread exits" { if (comptime threadSpecificDestructorPolicy() != .native_spawn_exit) return error.SkipZigTest; const key = try createThreadSpecificKey(ThreadSpecificProbe.destroy); var counter = std.atomic.Value(usize).init(0); const thread = try spawn(ThreadSpecificProbe.setValue, .{ key, &counter }); thread.join(); try std.testing.expectEqual(@as(usize, 1), counter.load(.monotonic));}test "native thread-specific values are isolated per sys thread" { if (comptime threadSpecificDestructorPolicy() != .native_spawn_exit) return error.SkipZigTest; const key = try createThreadSpecificKey(ThreadSpecificProbe.destroy); var main_counter = std.atomic.Value(usize).init(0); try std.testing.expect(setThreadSpecificValue(key, &main_counter)); defer _ = setThreadSpecificValue(key, null); var thread_missing_main = false; var thread_value: usize = 0; var thread_result: usize = 0; const thread = try spawn(ThreadSpecificProbe.isolate, .{ key, &thread_missing_main, &thread_value, &thread_result }); thread.join(); try std.testing.expect(thread_missing_main); try std.testing.expectEqual(@intFromPtr(&thread_value), thread_result); try std.testing.expectEqual(@intFromPtr(&main_counter), @intFromPtr(getThreadSpecificValue(key) orelse return error.MainThreadSpecificValueMissing));}Complete caller list for thread.spawn
12 direct callers.
tiny.http.ThreadPool[function] atlib/http/src/pool.zig:24lib.http.src.pool.test_ThreadPool_accepts_concurrent_producers[function] — test source atlib/http/src/pool.zig:835in nearest public ownerlib.http.src.poollib.http.src.server.test.test_Server_accepts_connection[function] — test source atlib/http/src/server/test.zig:114in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_closes_overload_and_reuses_bounded_connection_slots[function] — test source atlib/http/src/server/test.zig:197in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_expires_idle_connections_without_occupying_a_worker[function] — test source atlib/http/src/server/test.zig:376in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_schedules_HTTP_and_WebSocket_turns_beyond_idle_upgrades[function] — test source atlib/http/src/server/test.zig:401in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_schedules_around_a_partial_HTTP_request[function] — test source atlib/http/src/server/test.zig:341in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_schedules_fresh_requests_beyond_idle_keepalive_count[function] — test source atlib/http/src/server/test.zig:302in nearest public ownerlib.http.src.server.testlib.http.src.server.test.test_Server_stop_unblocks_listen[function] — test source atlib/http/src/server/test.zig:86in nearest public ownerlib.http.src.server.testlib.sys.src.thread.test_futex_boundary_waits_on_and_wakes_an_atomic_word[function] — test source atlib/sys/src/thread.zig:332in nearest public ownertiny.sys.threadlib.sys.src.thread.test_native_thread-specific_destructor_runs_when_sys-spawned_thread_exits[function] — test source atlib/sys/src/thread.zig:368in nearest public ownertiny.sys.threadlib.sys.src.thread.test_native_thread-specific_values_are_isolated_per_sys_thread[function] — test source atlib/sys/src/thread.zig:379in nearest public ownertiny.sys.thread
Complete caller list for thread.threadsSupported
8 direct callers.
tiny.sys.thread.cpuCount[function] atlib/sys/src/thread.zig:232tiny.sys.thread.currentId[function] atlib/sys/src/thread.zig:237tiny.sys.thread.spawn[function] atlib/sys/src/thread.zig:213lib.sys.src.thread.test_futex_boundary_waits_on_and_wakes_an_atomic_word[function] — test source atlib/sys/src/thread.zig:332in nearest public ownertiny.sys.threadlib.sys.src.thread.test_thread-specific_destructor_support_uses_sys-owned_thread_exit_policy[function] — test source atlib/sys/src/thread.zig:292in nearest public ownertiny.sys.threadlib.sys.src.thread.test_thread_support_follows_host_capabilities[function] — test source atlib/sys/src/thread.zig:298in nearest public ownertiny.sys.threadtiny.sys.thread.threadSpecificDestructorPolicy[function] atlib/sys/src/thread.zig:263tiny.sys.thread.yield[function] atlib/sys/src/thread.zig:227
Audit
| Definitions | 46 |
|---|---|
| Public names | 46 |
| Members | 19 |
| Version | 26.7.0 |
| Revision | daab053ee433 |