master
   1//! This struct represents a kernel thread, and acts as a namespace for
   2//! concurrency primitives that operate on kernel threads. For concurrency
   3//! primitives that interact with the I/O interface, see `std.Io`.
   4
   5const builtin = @import("builtin");
   6const target = builtin.target;
   7const native_os = builtin.os.tag;
   8
   9const std = @import("std.zig");
  10const math = std.math;
  11const assert = std.debug.assert;
  12const posix = std.posix;
  13const windows = std.os.windows;
  14const testing = std.testing;
  15
  16pub const Futex = @import("Thread/Futex.zig");
  17pub const Mutex = @import("Thread/Mutex.zig");
  18pub const Semaphore = @import("Thread/Semaphore.zig");
  19pub const Condition = @import("Thread/Condition.zig");
  20pub const RwLock = @import("Thread/RwLock.zig");
  21pub const Pool = @import("Thread/Pool.zig");
  22pub const WaitGroup = @import("Thread/WaitGroup.zig");
  23
  24pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;
  25
  26/// A thread-safe logical boolean value which can be `set` and `unset`.
  27///
  28/// It can also block threads until the value is set with cancelation via timed
  29/// waits. Statically initializable; four bytes on all targets.
  30pub const ResetEvent = enum(u32) {
  31    unset = 0,
  32    waiting = 1,
  33    is_set = 2,
  34
  35    /// Returns whether the logical boolean is `set`.
  36    ///
  37    /// Once `reset` is called, this returns false until the next `set`.
  38    ///
  39    /// The memory accesses before the `set` can be said to happen before
  40    /// `isSet` returns true.
  41    pub fn isSet(re: *const ResetEvent) bool {
  42        if (builtin.single_threaded) return switch (re.*) {
  43            .unset => false,
  44            .waiting => unreachable,
  45            .is_set => true,
  46        };
  47        // Acquire barrier ensures memory accesses before `set` happen before
  48        // returning true.
  49        return @atomicLoad(ResetEvent, re, .acquire) == .is_set;
  50    }
  51
  52    /// Blocks the calling thread until `set` is called.
  53    ///
  54    /// This is effectively a more efficient version of `while (!isSet()) {}`.
  55    ///
  56    /// The memory accesses before the `set` can be said to happen before `wait` returns.
  57    pub fn wait(re: *ResetEvent) void {
  58        if (builtin.single_threaded) switch (re.*) {
  59            .unset => unreachable, // Deadlock, no other threads to wake us up.
  60            .waiting => unreachable, // Invalid state.
  61            .is_set => return,
  62        };
  63        if (!re.isSet()) return timedWaitInner(re, null) catch |err| switch (err) {
  64            error.Timeout => unreachable, // No timeout specified.
  65        };
  66    }
  67
  68    /// Blocks the calling thread until `set` is called, or until the
  69    /// corresponding timeout expires, returning `error.Timeout`.
  70    ///
  71    /// This is effectively a more efficient version of `while (!isSet()) {}`.
  72    ///
  73    /// The memory accesses before the set() can be said to happen before
  74    /// timedWait() returns without error.
  75    pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void {
  76        if (builtin.single_threaded) switch (re.*) {
  77            .unset => return error.Timeout,
  78            .waiting => unreachable, // Invalid state.
  79            .is_set => return,
  80        };
  81        if (!re.isSet()) return timedWaitInner(re, timeout_ns);
  82    }
  83
  84    fn timedWaitInner(re: *ResetEvent, timeout: ?u64) error{Timeout}!void {
  85        @branchHint(.cold);
  86
  87        // Try to set the state from `unset` to `waiting` to indicate to the
  88        // `set` thread that others are blocked on the ResetEvent. Avoid using
  89        // any strict barriers until we know the ResetEvent is set.
  90        var state = @atomicLoad(ResetEvent, re, .acquire);
  91        if (state == .unset) {
  92            state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting;
  93        }
  94
  95        // Wait until the ResetEvent is set since the state is waiting.
  96        if (state == .waiting) {
  97            var futex_deadline = Futex.Deadline.init(timeout);
  98            while (true) {
  99                const wait_result = futex_deadline.wait(@ptrCast(re), @intFromEnum(ResetEvent.waiting));
 100
 101                // Check if the ResetEvent was set before possibly reporting error.Timeout below.
 102                state = @atomicLoad(ResetEvent, re, .acquire);
 103                if (state != .waiting) break;
 104
 105                try wait_result;
 106            }
 107        }
 108
 109        assert(state == .is_set);
 110    }
 111
 112    /// Marks the logical boolean as `set` and unblocks any threads in `wait`
 113    /// or `timedWait` to observe the new state.
 114    ///
 115    /// The logical boolean stays `set` until `reset` is called, making future
 116    /// `set` calls do nothing semantically.
 117    ///
 118    /// The memory accesses before `set` can be said to happen before `isSet`
 119    /// returns true or `wait`/`timedWait` return successfully.
 120    pub fn set(re: *ResetEvent) void {
 121        if (builtin.single_threaded) {
 122            re.* = .is_set;
 123            return;
 124        }
 125        if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) {
 126            Futex.wake(@ptrCast(re), std.math.maxInt(u32));
 127        }
 128    }
 129
 130    /// Unmarks the ResetEvent as if `set` was never called.
 131    ///
 132    /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent
 133    /// calls to `set`, `isSet` and `reset` are allowed.
 134    pub fn reset(re: *ResetEvent) void {
 135        if (builtin.single_threaded) {
 136            re.* = .unset;
 137            return;
 138        }
 139        @atomicStore(ResetEvent, re, .unset, .monotonic);
 140    }
 141};
 142
 143const Thread = @This();
 144const Impl = if (native_os == .windows)
 145    WindowsThreadImpl
 146else if (use_pthreads)
 147    PosixThreadImpl
 148else if (native_os == .linux)
 149    LinuxThreadImpl
 150else if (native_os == .wasi)
 151    WasiThreadImpl
 152else
 153    UnsupportedImpl;
 154
 155impl: Impl,
 156
 157pub const max_name_len = switch (native_os) {
 158    .linux => 15,
 159    .windows => 31,
 160    .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => 63,
 161    .netbsd => 31,
 162    .freebsd => 15,
 163    .openbsd => 23,
 164    .dragonfly => 1023,
 165    .illumos => 31,
 166    // https://github.com/SerenityOS/serenity/blob/6b4c300353da49d3508b5442cf61da70bd04d757/Kernel/Tasks/Thread.h#L102
 167    .serenity => 63,
 168    else => 0,
 169};
 170
 171pub const SetNameError = error{
 172    NameTooLong,
 173    Unsupported,
 174    Unexpected,
 175    InvalidWtf8,
 176} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
 177
 178pub fn setName(self: Thread, name: []const u8) SetNameError!void {
 179    if (name.len > max_name_len) return error.NameTooLong;
 180
 181    const name_with_terminator = blk: {
 182        var name_buf: [max_name_len:0]u8 = undefined;
 183        @memcpy(name_buf[0..name.len], name);
 184        name_buf[name.len] = 0;
 185        break :blk name_buf[0..name.len :0];
 186    };
 187
 188    switch (native_os) {
 189        .linux => if (use_pthreads) {
 190            if (self.getHandle() == std.c.pthread_self()) {
 191                // Set the name of the calling thread (no thread id required).
 192                const err = try posix.prctl(.SET_NAME, .{@intFromPtr(name_with_terminator.ptr)});
 193                switch (@as(posix.E, @enumFromInt(err))) {
 194                    .SUCCESS => return,
 195                    else => |e| return posix.unexpectedErrno(e),
 196                }
 197            } else {
 198                const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
 199                switch (@as(posix.E, @enumFromInt(err))) {
 200                    .SUCCESS => return,
 201                    .RANGE => unreachable,
 202                    else => |e| return posix.unexpectedErrno(e),
 203                }
 204            }
 205        } else {
 206            var buf: [32]u8 = undefined;
 207            const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
 208
 209            const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only });
 210            defer file.close();
 211
 212            try file.writeAll(name);
 213            return;
 214        },
 215        .windows => {
 216            var buf: [max_name_len]u16 = undefined;
 217            const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
 218            const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
 219
 220            // Note: NT allocates its own copy, no use-after-free here.
 221            const unicode_string = windows.UNICODE_STRING{
 222                .Length = byte_len,
 223                .MaximumLength = byte_len,
 224                .Buffer = &buf,
 225            };
 226
 227            switch (windows.ntdll.NtSetInformationThread(
 228                self.getHandle(),
 229                .ThreadNameInformation,
 230                &unicode_string,
 231                @sizeOf(windows.UNICODE_STRING),
 232            )) {
 233                .SUCCESS => return,
 234                .NOT_IMPLEMENTED => return error.Unsupported,
 235                else => |err| return windows.unexpectedStatus(err),
 236            }
 237        },
 238        .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => if (use_pthreads) {
 239            // There doesn't seem to be a way to set the name for an arbitrary thread, only the current one.
 240            if (self.getHandle() != std.c.pthread_self()) return error.Unsupported;
 241
 242            const err = std.c.pthread_setname_np(name_with_terminator.ptr);
 243            switch (@as(posix.E, @enumFromInt(err))) {
 244                .SUCCESS => return,
 245                else => |e| return posix.unexpectedErrno(e),
 246            }
 247        },
 248        .serenity => if (use_pthreads) {
 249            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
 250            switch (@as(posix.E, @enumFromInt(err))) {
 251                .SUCCESS => return,
 252                .NAMETOOLONG => unreachable,
 253                .SRCH => unreachable,
 254                else => |e| return posix.unexpectedErrno(e),
 255            }
 256        },
 257        .netbsd, .illumos => if (use_pthreads) {
 258            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null);
 259            switch (@as(posix.E, @enumFromInt(err))) {
 260                .SUCCESS => return,
 261                .INVAL => unreachable,
 262                .SRCH => unreachable,
 263                .NOMEM => unreachable,
 264                else => |e| return posix.unexpectedErrno(e),
 265            }
 266        },
 267        .freebsd, .openbsd => if (use_pthreads) {
 268            // Use pthread_set_name_np for FreeBSD because pthread_setname_np is FreeBSD 12.2+ only.
 269            // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because
 270            // pthread_setname_np can return an error.
 271
 272            std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr);
 273            return;
 274        },
 275        .dragonfly => if (use_pthreads) {
 276            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
 277            switch (@as(posix.E, @enumFromInt(err))) {
 278                .SUCCESS => return,
 279                .INVAL => unreachable,
 280                .FAULT => unreachable,
 281                .NAMETOOLONG => unreachable, // already checked
 282                .SRCH => unreachable,
 283                else => |e| return posix.unexpectedErrno(e),
 284            }
 285        },
 286        else => {},
 287    }
 288    return error.Unsupported;
 289}
 290
 291pub const GetNameError = error{
 292    Unsupported,
 293    Unexpected,
 294} || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
 295
 296/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
 297/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 298pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
 299    buffer_ptr[max_name_len] = 0;
 300    var buffer: [:0]u8 = buffer_ptr;
 301
 302    switch (native_os) {
 303        .linux => if (use_pthreads) {
 304            if (self.getHandle() == std.c.pthread_self()) {
 305                // Get the name of the calling thread (no thread id required).
 306                const err = try posix.prctl(.GET_NAME, .{@intFromPtr(buffer.ptr)});
 307                switch (@as(posix.E, @enumFromInt(err))) {
 308                    .SUCCESS => return std.mem.sliceTo(buffer, 0),
 309                    else => |e| return posix.unexpectedErrno(e),
 310                }
 311            } else {
 312                const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 313                switch (@as(posix.E, @enumFromInt(err))) {
 314                    .SUCCESS => return std.mem.sliceTo(buffer, 0),
 315                    .RANGE => unreachable,
 316                    else => |e| return posix.unexpectedErrno(e),
 317                }
 318            }
 319        } else {
 320            var buf: [32]u8 = undefined;
 321            const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
 322
 323            var threaded: std.Io.Threaded = .init_single_threaded;
 324            const io = threaded.ioBasic();
 325
 326            const file = try std.fs.cwd().openFile(path, .{});
 327            defer file.close();
 328
 329            var file_reader = file.readerStreaming(io, &.{});
 330            const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
 331                error.ReadFailed => return file_reader.err.?,
 332            };
 333            return if (data_len >= 1) buffer[0 .. data_len - 1] else null;
 334        },
 335        .windows => {
 336            const buf_capacity = @sizeOf(windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len);
 337            var buf: [buf_capacity]u8 align(@alignOf(windows.UNICODE_STRING)) = undefined;
 338
 339            switch (windows.ntdll.NtQueryInformationThread(
 340                self.getHandle(),
 341                .ThreadNameInformation,
 342                &buf,
 343                buf_capacity,
 344                null,
 345            )) {
 346                .SUCCESS => {
 347                    const string = @as(*const windows.UNICODE_STRING, @ptrCast(&buf));
 348                    const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]);
 349                    return if (len > 0) buffer[0..len] else null;
 350                },
 351                .NOT_IMPLEMENTED => return error.Unsupported,
 352                else => |err| return windows.unexpectedStatus(err),
 353            }
 354        },
 355        .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => if (use_pthreads) {
 356            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 357            switch (@as(posix.E, @enumFromInt(err))) {
 358                .SUCCESS => return std.mem.sliceTo(buffer, 0),
 359                .SRCH => unreachable,
 360                else => |e| return posix.unexpectedErrno(e),
 361            }
 362        },
 363        .serenity => if (use_pthreads) {
 364            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 365            switch (@as(posix.E, @enumFromInt(err))) {
 366                .SUCCESS => return,
 367                .NAMETOOLONG => unreachable,
 368                .SRCH => unreachable,
 369                .FAULT => unreachable,
 370                else => |e| return posix.unexpectedErrno(e),
 371            }
 372        },
 373        .netbsd, .illumos => if (use_pthreads) {
 374            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 375            switch (@as(posix.E, @enumFromInt(err))) {
 376                .SUCCESS => return std.mem.sliceTo(buffer, 0),
 377                .INVAL => unreachable,
 378                .SRCH => unreachable,
 379                else => |e| return posix.unexpectedErrno(e),
 380            }
 381        },
 382        .freebsd, .openbsd => if (use_pthreads) {
 383            // Use pthread_get_name_np for FreeBSD because pthread_getname_np is FreeBSD 12.2+ only.
 384            // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_getname_np can return an error.
 385
 386            std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 387            return std.mem.sliceTo(buffer, 0);
 388        },
 389        .dragonfly => if (use_pthreads) {
 390            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
 391            switch (@as(posix.E, @enumFromInt(err))) {
 392                .SUCCESS => return std.mem.sliceTo(buffer, 0),
 393                .INVAL => unreachable,
 394                .FAULT => unreachable,
 395                .SRCH => unreachable,
 396                else => |e| return posix.unexpectedErrno(e),
 397            }
 398        },
 399        else => {},
 400    }
 401    return error.Unsupported;
 402}
 403
 404/// Represents an ID per thread guaranteed to be unique only within a process.
 405pub const Id = switch (native_os) {
 406    .linux,
 407    .dragonfly,
 408    .netbsd,
 409    .freebsd,
 410    .openbsd,
 411    .haiku,
 412    .wasi,
 413    .serenity,
 414    => u32,
 415    .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u64,
 416    .windows => windows.DWORD,
 417    else => usize,
 418};
 419
 420/// Returns the platform ID of the callers thread.
 421/// Attempts to use thread locals and avoid syscalls when possible.
 422pub fn getCurrentId() Id {
 423    return Impl.getCurrentId();
 424}
 425
 426pub const CpuCountError = error{
 427    PermissionDenied,
 428    SystemResources,
 429    Unsupported,
 430    Unexpected,
 431};
 432
 433/// Returns the platforms view on the number of logical CPU cores available.
 434///
 435/// Returned value guaranteed to be >= 1.
 436pub fn getCpuCount() CpuCountError!usize {
 437    return try Impl.getCpuCount();
 438}
 439
 440/// Configuration options for hints on how to spawn threads.
 441pub const SpawnConfig = struct {
 442    // TODO compile-time call graph analysis to determine stack upper bound
 443    // https://github.com/ziglang/zig/issues/157
 444
 445    /// Size in bytes of the Thread's stack
 446    stack_size: usize = default_stack_size,
 447    /// The allocator to be used to allocate memory for the to-be-spawned thread
 448    allocator: ?std.mem.Allocator = null,
 449
 450    pub const default_stack_size = 16 * 1024 * 1024;
 451};
 452
 453pub const SpawnError = error{
 454    /// A system-imposed limit on the number of threads was encountered.
 455    /// There are a number of limits that may trigger this error:
 456    /// *  the  RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
 457    ///    which limits the number of processes and threads for  a  real
 458    ///    user ID, was reached;
 459    /// *  the kernel's system-wide limit on the number of processes and
 460    ///    threads,  /proc/sys/kernel/threads-max,  was   reached   (see
 461    ///    proc(5));
 462    /// *  the  maximum  number  of  PIDs, /proc/sys/kernel/pid_max, was
 463    ///    reached (see proc(5)); or
 464    /// *  the PID limit (pids.max) imposed by the cgroup "process  num‐
 465    ///    ber" (PIDs) controller was reached.
 466    ThreadQuotaExceeded,
 467
 468    /// The kernel cannot allocate sufficient memory to allocate a task structure
 469    /// for the child, or to copy those parts of the caller's context that need to
 470    /// be copied.
 471    SystemResources,
 472
 473    /// Not enough userland memory to spawn the thread.
 474    OutOfMemory,
 475
 476    /// `mlockall` is enabled, and the memory needed to spawn the thread
 477    /// would exceed the limit.
 478    LockedMemoryLimitExceeded,
 479
 480    Unexpected,
 481};
 482
 483/// Spawns a new thread which executes `function` using `args` and returns a handle to the spawned thread.
 484/// `config` can be used as hints to the platform for how to spawn and execute the `function`.
 485/// The caller must eventually either call `join()` to wait for the thread to finish and free its resources
 486/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion.
 487pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
 488    if (builtin.single_threaded) {
 489        @compileError("Cannot spawn thread when building in single-threaded mode");
 490    }
 491
 492    const impl = try Impl.spawn(config, function, args);
 493    return Thread{ .impl = impl };
 494}
 495
 496/// Represents a kernel thread handle.
 497/// May be an integer or a pointer depending on the platform.
 498pub const Handle = Impl.ThreadHandle;
 499
 500/// Returns the handle of this thread
 501pub fn getHandle(self: Thread) Handle {
 502    return self.impl.getHandle();
 503}
 504
 505/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
 506/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
 507pub fn detach(self: Thread) void {
 508    return self.impl.detach();
 509}
 510
 511/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
 512/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
 513pub fn join(self: Thread) void {
 514    return self.impl.join();
 515}
 516
 517pub const YieldError = error{
 518    /// The system is not configured to allow yielding
 519    SystemCannotYield,
 520};
 521
 522/// Yields the current thread potentially allowing other threads to run.
 523pub fn yield() YieldError!void {
 524    if (native_os == .windows) {
 525        // The return value has to do with how many other threads there are; it is not
 526        // an error condition on Windows.
 527        _ = windows.kernel32.SwitchToThread();
 528        return;
 529    }
 530    switch (posix.errno(posix.system.sched_yield())) {
 531        .SUCCESS => return,
 532        .NOSYS => return error.SystemCannotYield,
 533        else => return error.SystemCannotYield,
 534    }
 535}
 536
 537/// State to synchronize detachment of spawner thread to spawned thread
 538const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_riscv64) u32 else u8) {
 539    running,
 540    detached,
 541    completed,
 542});
 543
 544/// Used by the Thread implementations to call the spawned function with the arguments.
 545fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
 546    WindowsThreadImpl => windows.DWORD,
 547    LinuxThreadImpl => u8,
 548    PosixThreadImpl => ?*anyopaque,
 549    else => unreachable,
 550} {
 551    const default_value = if (Impl == PosixThreadImpl) null else 0;
 552    const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'";
 553
 554    switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
 555        .noreturn => {
 556            @call(.auto, f, args);
 557        },
 558        .void => {
 559            @call(.auto, f, args);
 560            return default_value;
 561        },
 562        .int => |info| {
 563            if (info.bits != 8) {
 564                @compileError(bad_fn_ret);
 565            }
 566
 567            const status = @call(.auto, f, args);
 568            if (Impl != PosixThreadImpl) {
 569                return status;
 570            }
 571
 572            // pthreads don't support exit status, ignore value
 573            return default_value;
 574        },
 575        .error_union => |info| {
 576            switch (info.payload) {
 577                void, noreturn => {
 578                    @call(.auto, f, args) catch |err| {
 579                        std.debug.print("error: {s}\n", .{@errorName(err)});
 580                        if (@errorReturnTrace()) |trace| {
 581                            std.debug.dumpStackTrace(trace);
 582                        }
 583                    };
 584
 585                    return default_value;
 586                },
 587                else => {
 588                    @compileError(bad_fn_ret);
 589                },
 590            }
 591        },
 592        else => {
 593            @compileError(bad_fn_ret);
 594        },
 595    }
 596}
 597
 598/// We can't compile error in the `Impl` switch statement as its eagerly evaluated.
 599/// So instead, we compile-error on the methods themselves for platforms which don't support threads.
 600const UnsupportedImpl = struct {
 601    pub const ThreadHandle = void;
 602
 603    fn getCurrentId() usize {
 604        return unsupported({});
 605    }
 606
 607    fn getCpuCount() !usize {
 608        return unsupported({});
 609    }
 610
 611    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
 612        return unsupported(.{ config, f, args });
 613    }
 614
 615    fn getHandle(self: Impl) ThreadHandle {
 616        return unsupported(self);
 617    }
 618
 619    fn detach(self: Impl) void {
 620        return unsupported(self);
 621    }
 622
 623    fn join(self: Impl) void {
 624        return unsupported(self);
 625    }
 626
 627    fn unsupported(unused: anytype) noreturn {
 628        _ = unused;
 629        @compileError("Unsupported operating system " ++ @tagName(native_os));
 630    }
 631};
 632
 633const WindowsThreadImpl = struct {
 634    pub const ThreadHandle = windows.HANDLE;
 635
 636    fn getCurrentId() windows.DWORD {
 637        return windows.GetCurrentThreadId();
 638    }
 639
 640    fn getCpuCount() !usize {
 641        // Faster than calling into GetSystemInfo(), even if amortized.
 642        return windows.peb().NumberOfProcessors;
 643    }
 644
 645    thread: *ThreadCompletion,
 646
 647    const ThreadCompletion = struct {
 648        completion: Completion,
 649        heap_ptr: windows.PVOID,
 650        heap_handle: windows.HANDLE,
 651        thread_handle: windows.HANDLE = undefined,
 652
 653        fn free(self: ThreadCompletion) void {
 654            const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr);
 655            assert(status != 0);
 656        }
 657    };
 658
 659    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
 660        const Args = @TypeOf(args);
 661        const Instance = struct {
 662            fn_args: Args,
 663            thread: ThreadCompletion,
 664
 665            fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.DWORD {
 666                const self: *@This() = @ptrCast(@alignCast(raw_ptr));
 667                defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
 668                    .running => {},
 669                    .completed => unreachable,
 670                    .detached => self.thread.free(),
 671                };
 672                return callFn(f, self.fn_args);
 673            }
 674        };
 675
 676        const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
 677        const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
 678        const alloc_ptr = windows.ntdll.RtlAllocateHeap(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory;
 679        errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0);
 680
 681        const instance_bytes = @as([*]u8, @ptrCast(alloc_ptr))[0..alloc_bytes];
 682        var fba = std.heap.FixedBufferAllocator.init(instance_bytes);
 683        const instance = fba.allocator().create(Instance) catch unreachable;
 684        instance.* = .{
 685            .fn_args = args,
 686            .thread = .{
 687                .completion = Completion.init(.running),
 688                .heap_ptr = alloc_ptr,
 689                .heap_handle = heap_handle,
 690            },
 691        };
 692
 693        // Windows appears to only support SYSTEM_INFO.dwAllocationGranularity minimum stack size.
 694        // Going lower makes it default to that specified in the executable (~1mb).
 695        // Its also fine if the limit here is incorrect as stack size is only a hint.
 696        var stack_size = std.math.cast(u32, config.stack_size) orelse std.math.maxInt(u32);
 697        stack_size = @max(64 * 1024, stack_size);
 698
 699        instance.thread.thread_handle = windows.kernel32.CreateThread(
 700            null,
 701            stack_size,
 702            Instance.entryFn,
 703            instance,
 704            0,
 705            null,
 706        ) orelse {
 707            const errno = windows.GetLastError();
 708            return windows.unexpectedError(errno);
 709        };
 710
 711        return Impl{ .thread = &instance.thread };
 712    }
 713
 714    fn getHandle(self: Impl) ThreadHandle {
 715        return self.thread.thread_handle;
 716    }
 717
 718    fn detach(self: Impl) void {
 719        windows.CloseHandle(self.thread.thread_handle);
 720        switch (self.thread.completion.swap(.detached, .seq_cst)) {
 721            .running => {},
 722            .completed => self.thread.free(),
 723            .detached => unreachable,
 724        }
 725    }
 726
 727    fn join(self: Impl) void {
 728        windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
 729        windows.CloseHandle(self.thread.thread_handle);
 730        assert(self.thread.completion.load(.seq_cst) == .completed);
 731        self.thread.free();
 732    }
 733};
 734
 735const PosixThreadImpl = struct {
 736    const c = std.c;
 737
 738    pub const ThreadHandle = c.pthread_t;
 739
 740    fn getCurrentId() Id {
 741        switch (native_os) {
 742            .linux => {
 743                return LinuxThreadImpl.getCurrentId();
 744            },
 745            .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
 746                var thread_id: u64 = undefined;
 747                // Pass thread=null to get the current thread ID.
 748                assert(c.pthread_threadid_np(null, &thread_id) == 0);
 749                return thread_id;
 750            },
 751            .dragonfly => {
 752                return @as(u32, @bitCast(c.lwp_gettid()));
 753            },
 754            .netbsd => {
 755                return @as(u32, @bitCast(c._lwp_self()));
 756            },
 757            .freebsd => {
 758                return @as(u32, @bitCast(c.pthread_getthreadid_np()));
 759            },
 760            .openbsd => {
 761                return @as(u32, @bitCast(c.getthrid()));
 762            },
 763            .haiku => {
 764                return @as(u32, @bitCast(c.find_thread(null)));
 765            },
 766            .serenity => {
 767                return @as(u32, @bitCast(c.pthread_self()));
 768            },
 769            else => {
 770                return @intFromPtr(c.pthread_self());
 771            },
 772        }
 773    }
 774
 775    fn getCpuCount() !usize {
 776        switch (native_os) {
 777            .linux => {
 778                return LinuxThreadImpl.getCpuCount();
 779            },
 780            .openbsd => {
 781                var count: c_int = undefined;
 782                var count_size: usize = @sizeOf(c_int);
 783                const mib = [_]c_int{ std.c.CTL.HW, std.c.HW.NCPUONLINE };
 784                posix.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
 785                    error.NameTooLong, error.UnknownName => unreachable,
 786                    else => |e| return e,
 787                };
 788                return @as(usize, @intCast(count));
 789            },
 790            .illumos, .serenity => {
 791                // The "proper" way to get the cpu count would be to query
 792                // /dev/kstat via ioctls, and traverse a linked list for each
 793                // cpu. (illumos)
 794                const rc = c.sysconf(@intFromEnum(std.c._SC.NPROCESSORS_ONLN));
 795                return switch (posix.errno(rc)) {
 796                    .SUCCESS => @as(usize, @intCast(rc)),
 797                    else => |err| posix.unexpectedErrno(err),
 798                };
 799            },
 800            .haiku => {
 801                var system_info: std.c.system_info = undefined;
 802                const rc = std.c.get_system_info(&system_info); // always returns B_OK
 803                return switch (posix.errno(rc)) {
 804                    .SUCCESS => @as(usize, @intCast(system_info.cpu_count)),
 805                    else => |err| posix.unexpectedErrno(err),
 806                };
 807            },
 808            else => {
 809                var count: c_int = undefined;
 810                var count_len: usize = @sizeOf(c_int);
 811                const name = if (comptime target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
 812                posix.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
 813                    error.UnknownName => unreachable,
 814                    else => |e| return e,
 815                };
 816                return @as(usize, @intCast(count));
 817            },
 818        }
 819    }
 820
 821    handle: ThreadHandle,
 822
 823    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
 824        const Args = @TypeOf(args);
 825        const allocator = std.heap.c_allocator;
 826
 827        const Instance = struct {
 828            fn entryFn(raw_arg: ?*anyopaque) callconv(.c) ?*anyopaque {
 829                const args_ptr: *Args = @ptrCast(@alignCast(raw_arg));
 830                defer allocator.destroy(args_ptr);
 831                return callFn(f, args_ptr.*);
 832            }
 833        };
 834
 835        const args_ptr = try allocator.create(Args);
 836        args_ptr.* = args;
 837        errdefer allocator.destroy(args_ptr);
 838
 839        var attr: c.pthread_attr_t = undefined;
 840        if (c.pthread_attr_init(&attr) != .SUCCESS) return error.SystemResources;
 841        defer assert(c.pthread_attr_destroy(&attr) == .SUCCESS);
 842
 843        // Use the same set of parameters used by the libc-less impl.
 844        const stack_size = @max(config.stack_size, 16 * 1024);
 845        assert(c.pthread_attr_setstacksize(&attr, stack_size) == .SUCCESS);
 846        assert(c.pthread_attr_setguardsize(&attr, std.heap.pageSize()) == .SUCCESS);
 847
 848        var handle: c.pthread_t = undefined;
 849        switch (c.pthread_create(
 850            &handle,
 851            &attr,
 852            Instance.entryFn,
 853            @ptrCast(args_ptr),
 854        )) {
 855            .SUCCESS => return Impl{ .handle = handle },
 856            .AGAIN => return error.SystemResources,
 857            .PERM => unreachable,
 858            .INVAL => unreachable,
 859            else => |err| return posix.unexpectedErrno(err),
 860        }
 861    }
 862
 863    fn getHandle(self: Impl) ThreadHandle {
 864        return self.handle;
 865    }
 866
 867    fn detach(self: Impl) void {
 868        switch (c.pthread_detach(self.handle)) {
 869            .SUCCESS => {},
 870            .INVAL => unreachable, // thread handle is not joinable
 871            .SRCH => unreachable, // thread handle is invalid
 872            else => unreachable,
 873        }
 874    }
 875
 876    fn join(self: Impl) void {
 877        switch (c.pthread_join(self.handle, null)) {
 878            .SUCCESS => {},
 879            .INVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
 880            .SRCH => unreachable, // thread handle is invalid
 881            .DEADLK => unreachable, // two threads tried to join each other
 882            else => unreachable,
 883        }
 884    }
 885};
 886
 887const WasiThreadImpl = struct {
 888    thread: *WasiThread,
 889
 890    pub const ThreadHandle = i32;
 891    threadlocal var tls_thread_id: Id = 0;
 892
 893    const WasiThread = struct {
 894        /// Thread ID
 895        tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(0),
 896        /// Contains all memory which was allocated to bootstrap this thread, including:
 897        /// - Guard page
 898        /// - Stack
 899        /// - TLS segment
 900        /// - `Instance`
 901        /// All memory is freed upon call to `join`
 902        memory: []u8,
 903        /// The allocator used to allocate the thread's memory,
 904        /// which is also used during `join` to ensure clean-up.
 905        allocator: std.mem.Allocator,
 906        /// The current state of the thread.
 907        state: State = State.init(.running),
 908    };
 909
 910    /// A meta-data structure used to bootstrap a thread
 911    const Instance = struct {
 912        thread: WasiThread,
 913        /// Contains the offset to the new __tls_base.
 914        /// The offset starting from the memory's base.
 915        tls_offset: usize,
 916        /// Contains the offset to the stack for the newly spawned thread.
 917        /// The offset is calculated starting from the memory's base.
 918        stack_offset: usize,
 919        /// Contains the raw pointer value to the wrapper which holds all arguments
 920        /// for the callback.
 921        raw_ptr: usize,
 922        /// Function pointer to a wrapping function which will call the user's
 923        /// function upon thread spawn. The above mentioned pointer will be passed
 924        /// to this function pointer as its argument.
 925        call_back: *const fn (usize) void,
 926        /// When a thread is in `detached` state, we must free all of its memory
 927        /// upon thread completion. However, as this is done while still within
 928        /// the thread, we must first jump back to the main thread's stack or else
 929        /// we end up freeing the stack that we're currently using.
 930        original_stack_pointer: [*]u8,
 931    };
 932
 933    const State = std.atomic.Value(enum(u8) { running, completed, detached });
 934
 935    fn getCurrentId() Id {
 936        return tls_thread_id;
 937    }
 938
 939    fn getCpuCount() error{Unsupported}!noreturn {
 940        return error.Unsupported;
 941    }
 942
 943    fn getHandle(self: Impl) ThreadHandle {
 944        return self.thread.tid.load(.seq_cst);
 945    }
 946
 947    fn detach(self: Impl) void {
 948        switch (self.thread.state.swap(.detached, .seq_cst)) {
 949            .running => {},
 950            .completed => self.join(),
 951            .detached => unreachable,
 952        }
 953    }
 954
 955    fn join(self: Impl) void {
 956        defer {
 957            // Create a copy of the allocator so we do not free the reference to the
 958            // original allocator while freeing the memory.
 959            var allocator = self.thread.allocator;
 960            allocator.free(self.thread.memory);
 961        }
 962
 963        while (true) {
 964            const tid = self.thread.tid.load(.seq_cst);
 965            if (tid == 0) break;
 966
 967            const result = asm (
 968                \\ local.get %[ptr]
 969                \\ local.get %[expected]
 970                \\ i64.const -1 # infinite
 971                \\ memory.atomic.wait32 0
 972                \\ local.set %[ret]
 973                : [ret] "=r" (-> u32),
 974                : [ptr] "r" (&self.thread.tid.raw),
 975                  [expected] "r" (tid),
 976            );
 977            switch (result) {
 978                0 => continue, // ok
 979                1 => continue, // expected =! loaded
 980                2 => unreachable, // timeout (infinite)
 981                else => unreachable,
 982            }
 983        }
 984    }
 985
 986    fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) SpawnError!WasiThreadImpl {
 987        if (config.allocator == null) {
 988            @panic("an allocator is required to spawn a WASI thread");
 989        }
 990
 991        // Wrapping struct required to hold the user-provided function arguments.
 992        const Wrapper = struct {
 993            args: @TypeOf(args),
 994            fn entry(ptr: usize) void {
 995                const w: *@This() = @ptrFromInt(ptr);
 996                const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
 997                switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
 998                    .noreturn, .void => {
 999                        @call(.auto, f, w.args);
1000                    },
1001                    .int => |info| {
1002                        if (info.bits != 8) {
1003                            @compileError(bad_fn_ret);
1004                        }
1005                        _ = @call(.auto, f, w.args); // WASI threads don't support exit status, ignore value
1006                    },
1007                    .error_union => |info| {
1008                        if (info.payload != void) {
1009                            @compileError(bad_fn_ret);
1010                        }
1011                        @call(.auto, f, w.args) catch |err| {
1012                            std.debug.print("error: {s}\n", .{@errorName(err)});
1013                            if (@errorReturnTrace()) |trace| {
1014                                std.debug.dumpStackTrace(trace);
1015                            }
1016                        };
1017                    },
1018                    else => {
1019                        @compileError(bad_fn_ret);
1020                    },
1021                }
1022            }
1023        };
1024
1025        var stack_offset: usize = undefined;
1026        var tls_offset: usize = undefined;
1027        var wrapper_offset: usize = undefined;
1028        var instance_offset: usize = undefined;
1029
1030        // Calculate the bytes we have to allocate to store all thread information, including:
1031        // - The actual stack for the thread
1032        // - The TLS segment
1033        // - `Instance` - containing information about how to call the user's function.
1034        const map_bytes = blk: {
1035            // start with atleast a single page, which is used as a guard to prevent
1036            // other threads clobbering our new thread.
1037            // Unfortunately, WebAssembly has no notion of read-only segments, so this
1038            // is only a best effort.
1039            var bytes: usize = std.wasm.page_size;
1040
1041            bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes
1042            stack_offset = bytes;
1043            bytes += @max(std.wasm.page_size, config.stack_size);
1044
1045            bytes = std.mem.alignForward(usize, bytes, __tls_align());
1046            tls_offset = bytes;
1047            bytes += __tls_size();
1048
1049            bytes = std.mem.alignForward(usize, bytes, @alignOf(Wrapper));
1050            wrapper_offset = bytes;
1051            bytes += @sizeOf(Wrapper);
1052
1053            bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
1054            instance_offset = bytes;
1055            bytes += @sizeOf(Instance);
1056
1057            bytes = std.mem.alignForward(usize, bytes, std.wasm.page_size);
1058            break :blk bytes;
1059        };
1060
1061        // Allocate the amount of memory required for all meta data.
1062        const allocated_memory = try config.allocator.?.alloc(u8, map_bytes);
1063
1064        const wrapper: *Wrapper = @ptrCast(@alignCast(&allocated_memory[wrapper_offset]));
1065        wrapper.* = .{ .args = args };
1066
1067        const instance: *Instance = @ptrCast(@alignCast(&allocated_memory[instance_offset]));
1068        instance.* = .{
1069            .thread = .{ .memory = allocated_memory, .allocator = config.allocator.? },
1070            .tls_offset = tls_offset,
1071            .stack_offset = stack_offset,
1072            .raw_ptr = @intFromPtr(wrapper),
1073            .call_back = &Wrapper.entry,
1074            .original_stack_pointer = __get_stack_pointer(),
1075        };
1076
1077        const tid = spawnWasiThread(instance);
1078        // The specification says any value lower than 0 indicates an error.
1079        // The values of such error are unspecified. WASI-Libc treats it as EAGAIN.
1080        if (tid < 0) {
1081            return error.SystemResources;
1082        }
1083        instance.thread.tid.store(tid, .seq_cst);
1084
1085        return .{ .thread = &instance.thread };
1086    }
1087
1088    comptime {
1089        if (!builtin.single_threaded) {
1090            @export(&wasi_thread_start, .{ .name = "wasi_thread_start" });
1091        }
1092    }
1093
1094    /// Called by the host environment after thread creation.
1095    fn wasi_thread_start(tid: i32, arg: *Instance) callconv(.c) void {
1096        comptime assert(!builtin.single_threaded);
1097        __set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset);
1098        __wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset);
1099        @atomicStore(u32, &WasiThreadImpl.tls_thread_id, @intCast(tid), .seq_cst);
1100
1101        // Finished bootstrapping, call user's procedure.
1102        arg.call_back(arg.raw_ptr);
1103
1104        switch (arg.thread.state.swap(.completed, .seq_cst)) {
1105            .running => {
1106                // reset the Thread ID
1107                asm volatile (
1108                    \\ local.get %[ptr]
1109                    \\ i32.const 0
1110                    \\ i32.atomic.store 0
1111                    :
1112                    : [ptr] "r" (&arg.thread.tid.raw),
1113                );
1114
1115                // Wake the main thread listening to this thread
1116                asm volatile (
1117                    \\ local.get %[ptr]
1118                    \\ i32.const 1 # waiters
1119                    \\ memory.atomic.notify 0
1120                    \\ drop # no need to know the waiters
1121                    :
1122                    : [ptr] "r" (&arg.thread.tid.raw),
1123                );
1124            },
1125            .completed => unreachable,
1126            .detached => {
1127                // restore the original stack pointer so we can free the memory
1128                // without having to worry about freeing the stack
1129                __set_stack_pointer(arg.original_stack_pointer);
1130                // Ensure a copy so we don't free the allocator reference itself
1131                var allocator = arg.thread.allocator;
1132                allocator.free(arg.thread.memory);
1133            },
1134        }
1135    }
1136
1137    /// Asks the host to create a new thread for us.
1138    /// Newly created thread will call `wasi_tread_start` with the thread ID as well
1139    /// as the input `arg` that was provided to `spawnWasiThread`
1140    const spawnWasiThread = @"thread-spawn";
1141    extern "wasi" fn @"thread-spawn"(arg: *Instance) i32;
1142
1143    /// Initializes the TLS data segment starting at `memory`.
1144    /// This is a synthetic function, generated by the linker.
1145    extern fn __wasm_init_tls(memory: [*]u8) void;
1146
1147    /// Returns a pointer to the base of the TLS data segment for the current thread
1148    inline fn __tls_base() [*]u8 {
1149        return asm (
1150            \\ .globaltype __tls_base, i32
1151            \\ global.get __tls_base
1152            \\ local.set %[ret]
1153            : [ret] "=r" (-> [*]u8),
1154        );
1155    }
1156
1157    /// Returns the size of the TLS segment
1158    inline fn __tls_size() u32 {
1159        return asm volatile (
1160            \\ .globaltype __tls_size, i32, immutable
1161            \\ global.get __tls_size
1162            \\ local.set %[ret]
1163            : [ret] "=r" (-> u32),
1164        );
1165    }
1166
1167    /// Returns the alignment of the TLS segment
1168    inline fn __tls_align() u32 {
1169        return asm (
1170            \\ .globaltype __tls_align, i32, immutable
1171            \\ global.get __tls_align
1172            \\ local.set %[ret]
1173            : [ret] "=r" (-> u32),
1174        );
1175    }
1176
1177    /// Allows for setting the stack pointer in the WebAssembly module.
1178    inline fn __set_stack_pointer(addr: [*]u8) void {
1179        asm volatile (
1180            \\ local.get %[ptr]
1181            \\ global.set __stack_pointer
1182            :
1183            : [ptr] "r" (addr),
1184        );
1185    }
1186
1187    /// Returns the current value of the stack pointer
1188    inline fn __get_stack_pointer() [*]u8 {
1189        return asm (
1190            \\ global.get __stack_pointer
1191            \\ local.set %[stack_ptr]
1192            : [stack_ptr] "=r" (-> [*]u8),
1193        );
1194    }
1195};
1196
1197const LinuxThreadImpl = struct {
1198    const linux = std.os.linux;
1199
1200    pub const ThreadHandle = i32;
1201
1202    threadlocal var tls_thread_id: ?Id = null;
1203
1204    fn getCurrentId() Id {
1205        return tls_thread_id orelse {
1206            const tid: u32 = @bitCast(linux.gettid());
1207            tls_thread_id = tid;
1208            return tid;
1209        };
1210    }
1211
1212    fn getCpuCount() !usize {
1213        const cpu_set = try posix.sched_getaffinity(0);
1214        return posix.CPU_COUNT(cpu_set);
1215    }
1216
1217    thread: *ThreadCompletion,
1218
1219    const ThreadCompletion = struct {
1220        completion: Completion = Completion.init(.running),
1221        child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1),
1222        parent_tid: i32 = undefined,
1223        mapped: []align(std.heap.page_size_min) u8,
1224
1225        /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
1226        /// Ported over from musl libc's pthread detached implementation:
1227        /// https://github.com/ifduyue/musl/search?q=__unmapself
1228        fn freeAndExit(self: *ThreadCompletion) noreturn {
1229            switch (target.cpu.arch) {
1230                .x86 => asm volatile (
1231                    \\  movl $91, %%eax # SYS_munmap
1232                    \\  movl %[ptr], %%ebx
1233                    \\  movl %[len], %%ecx
1234                    \\  int $128
1235                    \\  movl $1, %%eax # SYS_exit
1236                    \\  movl $0, %%ebx
1237                    \\  int $128
1238                    :
1239                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1240                      [len] "r" (self.mapped.len),
1241                    : .{ .memory = true }),
1242                .x86_64 => asm volatile (switch (target.abi) {
1243                        .gnux32, .muslx32 =>
1244                        \\  movl $0x4000000b, %%eax # SYS_munmap
1245                        \\  syscall
1246                        \\  movl $0x4000003c, %%eax # SYS_exit
1247                        \\  xor %%rdi, %%rdi
1248                        \\  syscall
1249                        ,
1250                        else =>
1251                        \\  movl $11, %%eax # SYS_munmap
1252                        \\  syscall
1253                        \\  movl $60, %%eax # SYS_exit
1254                        \\  xor %%rdi, %%rdi
1255                        \\  syscall
1256                        ,
1257                    }
1258                    :
1259                    : [ptr] "{rdi}" (@intFromPtr(self.mapped.ptr)),
1260                      [len] "{rsi}" (self.mapped.len),
1261                ),
1262                .arm, .armeb, .thumb, .thumbeb => asm volatile (
1263                    \\  mov r7, #91 // SYS_munmap
1264                    \\  mov r0, %[ptr]
1265                    \\  mov r1, %[len]
1266                    \\  svc 0
1267                    \\  mov r7, #1 // SYS_exit
1268                    \\  mov r0, #0
1269                    \\  svc 0
1270                    :
1271                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1272                      [len] "r" (self.mapped.len),
1273                    : .{ .memory = true }),
1274                .aarch64, .aarch64_be => asm volatile (
1275                    \\  mov x8, #215 // SYS_munmap
1276                    \\  mov x0, %[ptr]
1277                    \\  mov x1, %[len]
1278                    \\  svc 0
1279                    \\  mov x8, #93 // SYS_exit
1280                    \\  mov x0, #0
1281                    \\  svc 0
1282                    :
1283                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1284                      [len] "r" (self.mapped.len),
1285                    : .{ .memory = true }),
1286                .alpha => asm volatile (
1287                    \\ ldi $0, 73 # SYS_munmap
1288                    \\ mov %[ptr], $16
1289                    \\ mov %[len], $17
1290                    \\ callsys
1291                    \\ ldi $0, 1 # SYS_exit
1292                    \\ ldi $16, 0
1293                    \\ callsys
1294                    :
1295                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1296                      [len] "r" (self.mapped.len),
1297                    : .{ .memory = true }),
1298                .hexagon => asm volatile (
1299                    \\  r6 = #215 // SYS_munmap
1300                    \\  r0 = %[ptr]
1301                    \\  r1 = %[len]
1302                    \\  trap0(#1)
1303                    \\  r6 = #93 // SYS_exit
1304                    \\  r0 = #0
1305                    \\  trap0(#1)
1306                    :
1307                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1308                      [len] "r" (self.mapped.len),
1309                    : .{ .memory = true }),
1310                .hppa => asm volatile (
1311                    \\ ldi 91, %%r20 /* SYS_munmap */
1312                    \\ copy %[ptr], %%r26
1313                    \\ copy %[len], %%r25
1314                    \\ ble 0x100(%%sr2, %%r0)
1315                    \\ ldi 1, %%r20 /* SYS_exit */
1316                    \\ ldi 0, %%r26
1317                    \\ ble 0x100(%%sr2, %%r0)
1318                    :
1319                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1320                      [len] "r" (self.mapped.len),
1321                    : .{ .memory = true }),
1322                .m68k => asm volatile (
1323                    \\ move.l #91, %%d0 // SYS_munmap
1324                    \\ move.l %[ptr], %%d1
1325                    \\ move.l %[len], %%d2
1326                    \\ trap #0
1327                    \\ move.l #1, %%d0 // SYS_exit
1328                    \\ move.l #0, %%d1
1329                    \\ trap #0
1330                    :
1331                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1332                      [len] "r" (self.mapped.len),
1333                    : .{ .memory = true }),
1334                .microblaze, .microblazeel => asm volatile (
1335                    \\ ori r12, r0, 91 # SYS_munmap
1336                    \\ ori r5, %[ptr], 0
1337                    \\ ori r6, %[len], 0
1338                    \\ brki r14, 0x8
1339                    \\ ori r12, r0, 1 # SYS_exit
1340                    \\ or r5, r0, r0
1341                    \\ brki r14, 0x8
1342                    :
1343                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1344                      [len] "r" (self.mapped.len),
1345                    : .{ .memory = true }),
1346                // We set `sp` to the address of the current function as a workaround for a Linux
1347                // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.
1348                // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in
1349                // 7928eb0370d1133d0d8cd2f5ddfca19c309079d5.
1350                .mips, .mipsel => asm volatile (
1351                    \\ move $sp, $t9
1352                    \\ li $v0, 4091 # SYS_munmap
1353                    \\ move $a0, %[ptr]
1354                    \\ move $a1, %[len]
1355                    \\ syscall
1356                    \\ li $v0, 4001 # SYS_exit
1357                    \\ li $a0, 0
1358                    \\ syscall
1359                    :
1360                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1361                      [len] "r" (self.mapped.len),
1362                    : .{ .memory = true }),
1363                .mips64, .mips64el => asm volatile (switch (target.abi) {
1364                        .gnuabin32, .muslabin32 =>
1365                        \\ li $v0, 6011 # SYS_munmap
1366                        \\ move $a0, %[ptr]
1367                        \\ move $a1, %[len]
1368                        \\ syscall
1369                        \\ li $v0, 6058 # SYS_exit
1370                        \\ li $a0, 0
1371                        \\ syscall
1372                        ,
1373                        else =>
1374                        \\ li $v0, 5011 # SYS_munmap
1375                        \\ move $a0, %[ptr]
1376                        \\ move $a1, %[len]
1377                        \\ syscall
1378                        \\ li $v0, 5058 # SYS_exit
1379                        \\ li $a0, 0
1380                        \\ syscall
1381                        ,
1382                    }
1383                    :
1384                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1385                      [len] "r" (self.mapped.len),
1386                    : .{ .memory = true }),
1387                .or1k => asm volatile (
1388                    \\ l.ori r11, r0, 215 # SYS_munmap
1389                    \\ l.ori r3, %[ptr]
1390                    \\ l.ori r4, %[len]
1391                    \\ l.sys 1
1392                    \\ l.ori r11, r0, 93 # SYS_exit
1393                    \\ l.ori r3, r0, r0
1394                    \\ l.sys 1
1395                    :
1396                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1397                      [len] "r" (self.mapped.len),
1398                    : .{ .memory = true }),
1399                .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
1400                    \\  li 0, 91 # SYS_munmap
1401                    \\  mr 3, %[ptr]
1402                    \\  mr 4, %[len]
1403                    \\  sc
1404                    \\  li 0, 1 # SYS_exit
1405                    \\  li 3, 0
1406                    \\  sc
1407                    \\  blr
1408                    :
1409                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1410                      [len] "r" (self.mapped.len),
1411                    : .{ .memory = true }),
1412                .riscv32, .riscv64 => asm volatile (
1413                    \\  li a7, 215 # SYS_munmap
1414                    \\  mv a0, %[ptr]
1415                    \\  mv a1, %[len]
1416                    \\  ecall
1417                    \\  li a7, 93 # SYS_exit
1418                    \\  mv a0, zero
1419                    \\  ecall
1420                    :
1421                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1422                      [len] "r" (self.mapped.len),
1423                    : .{ .memory = true }),
1424                .s390x => asm volatile (
1425                    \\  lgr %%r2, %[ptr]
1426                    \\  lgr %%r3, %[len]
1427                    \\  svc 91 # SYS_munmap
1428                    \\  lghi %%r2, 0
1429                    \\  svc 1 # SYS_exit
1430                    :
1431                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1432                      [len] "r" (self.mapped.len),
1433                    : .{ .memory = true }),
1434                .sh, .sheb => asm volatile (
1435                    \\ mov #91, r3 ! SYS_munmap
1436                    \\ mov %[ptr], r4
1437                    \\ mov %[len], r5
1438                    \\ trapa #31
1439                    \\ or r0, r0
1440                    \\ or r0, r0
1441                    \\ or r0, r0
1442                    \\ or r0, r0
1443                    \\ or r0, r0
1444                    \\ mov #1, r3 ! SYS_exit
1445                    \\ mov #0, r4
1446                    \\ trapa #31
1447                    \\ or r0, r0
1448                    \\ or r0, r0
1449                    \\ or r0, r0
1450                    \\ or r0, r0
1451                    \\ or r0, r0
1452                    :
1453                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1454                      [len] "r" (self.mapped.len),
1455                    : .{ .memory = true }),
1456                .sparc => asm volatile (
1457                    \\ # See sparc64 comments below.
1458                    \\ 1:
1459                    \\  cmp %%fp, 0
1460                    \\  beq 2f
1461                    \\  nop
1462                    \\  ba 1b
1463                    \\  restore
1464                    \\ 2:
1465                    \\  mov 73, %%g1 // SYS_munmap
1466                    \\  mov %[ptr], %%o0
1467                    \\  mov %[len], %%o1
1468                    \\  t 0x3 # ST_FLUSH_WINDOWS
1469                    \\  t 0x10
1470                    \\  mov 1, %%g1 // SYS_exit
1471                    \\  mov 0, %%o0
1472                    \\  t 0x10
1473                    :
1474                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1475                      [len] "r" (self.mapped.len),
1476                    : .{ .memory = true }),
1477                .sparc64 => asm volatile (
1478                    \\ # SPARCs really don't like it when active stack frames
1479                    \\ # is unmapped (it will result in a segfault), so we
1480                    \\ # force-deactivate it by running `restore` until
1481                    \\ # all frames are cleared.
1482                    \\ 1:
1483                    \\  cmp %%fp, 0
1484                    \\  beq 2f
1485                    \\  nop
1486                    \\  ba 1b
1487                    \\  restore
1488                    \\ 2:
1489                    \\  mov 73, %%g1 // SYS_munmap
1490                    \\  mov %[ptr], %%o0
1491                    \\  mov %[len], %%o1
1492                    \\  # Flush register window contents to prevent background
1493                    \\  # memory access before unmapping the stack.
1494                    \\  flushw
1495                    \\  t 0x6d
1496                    \\  mov 1, %%g1 // SYS_exit
1497                    \\  mov 0, %%o0
1498                    \\  t 0x6d
1499                    :
1500                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1501                      [len] "r" (self.mapped.len),
1502                    : .{ .memory = true }),
1503                .loongarch32, .loongarch64 => asm volatile (
1504                    \\ or      $a0, $zero, %[ptr]
1505                    \\ or      $a1, $zero, %[len]
1506                    \\ ori     $a7, $zero, 215     # SYS_munmap
1507                    \\ syscall 0                   # call munmap
1508                    \\ ori     $a0, $zero, 0
1509                    \\ ori     $a7, $zero, 93      # SYS_exit
1510                    \\ syscall 0                   # call exit
1511                    :
1512                    : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1513                      [len] "r" (self.mapped.len),
1514                    : .{ .memory = true }),
1515                else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
1516            }
1517            unreachable;
1518        }
1519    };
1520
1521    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
1522        const page_size = std.heap.pageSize();
1523        const Args = @TypeOf(args);
1524        const Instance = struct {
1525            fn_args: Args,
1526            thread: ThreadCompletion,
1527
1528            fn entryFn(raw_arg: usize) callconv(.c) u8 {
1529                const self = @as(*@This(), @ptrFromInt(raw_arg));
1530                defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
1531                    .running => {},
1532                    .completed => unreachable,
1533                    .detached => self.thread.freeAndExit(),
1534                };
1535                return callFn(f, self.fn_args);
1536            }
1537        };
1538
1539        var guard_offset: usize = undefined;
1540        var stack_offset: usize = undefined;
1541        var tls_offset: usize = undefined;
1542        var instance_offset: usize = undefined;
1543
1544        const map_bytes = blk: {
1545            var bytes: usize = page_size;
1546            guard_offset = bytes;
1547
1548            bytes += @max(page_size, config.stack_size);
1549            bytes = std.mem.alignForward(usize, bytes, page_size);
1550            stack_offset = bytes;
1551
1552            bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment);
1553            tls_offset = bytes;
1554            bytes += linux.tls.area_desc.size;
1555
1556            bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
1557            instance_offset = bytes;
1558            bytes += @sizeOf(Instance);
1559
1560            bytes = std.mem.alignForward(usize, bytes, page_size);
1561            break :blk bytes;
1562        };
1563
1564        // map all memory needed without read/write permissions
1565        // to avoid committing the whole region right away
1566        // anonymous mapping ensures file descriptor limits are not exceeded
1567        const mapped = posix.mmap(
1568            null,
1569            map_bytes,
1570            posix.PROT.NONE,
1571            .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
1572            -1,
1573            0,
1574        ) catch |err| switch (err) {
1575            error.MemoryMappingNotSupported => unreachable,
1576            error.AccessDenied => unreachable,
1577            error.PermissionDenied => unreachable,
1578            error.ProcessFdQuotaExceeded => unreachable,
1579            error.SystemFdQuotaExceeded => unreachable,
1580            error.MappingAlreadyExists => unreachable,
1581            else => |e| return e,
1582        };
1583        assert(mapped.len >= map_bytes);
1584        errdefer posix.munmap(mapped);
1585
1586        // map everything but the guard page as read/write
1587        posix.mprotect(
1588            @alignCast(mapped[guard_offset..]),
1589            posix.PROT.READ | posix.PROT.WRITE,
1590        ) catch |err| switch (err) {
1591            error.AccessDenied => unreachable,
1592            else => |e| return e,
1593        };
1594
1595        // Prepare the TLS segment and prepare a user_desc struct when needed on x86
1596        var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..]);
1597        var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined;
1598        if (target.cpu.arch == .x86) {
1599            defer tls_ptr = @intFromPtr(&user_desc);
1600            user_desc = .{
1601                .entry_number = linux.tls.area_desc.gdt_entry_number,
1602                .base_addr = tls_ptr,
1603                .limit = 0xfffff,
1604                .flags = .{
1605                    .seg_32bit = 1,
1606                    .contents = 0, // Data
1607                    .read_exec_only = 0,
1608                    .limit_in_pages = 1,
1609                    .seg_not_present = 0,
1610                    .useable = 1,
1611                },
1612            };
1613        }
1614
1615        const instance: *Instance = @ptrCast(@alignCast(&mapped[instance_offset]));
1616        instance.* = .{
1617            .fn_args = args,
1618            .thread = .{ .mapped = mapped },
1619        };
1620
1621        const flags: u32 = linux.CLONE.THREAD | linux.CLONE.DETACHED |
1622            linux.CLONE.VM | linux.CLONE.FS | linux.CLONE.FILES |
1623            linux.CLONE.PARENT_SETTID | linux.CLONE.CHILD_CLEARTID |
1624            linux.CLONE.SIGHAND | linux.CLONE.SYSVSEM | linux.CLONE.SETTLS;
1625
1626        switch (linux.errno(linux.clone(
1627            Instance.entryFn,
1628            @intFromPtr(&mapped[stack_offset]),
1629            flags,
1630            @intFromPtr(instance),
1631            &instance.thread.parent_tid,
1632            tls_ptr,
1633            &instance.thread.child_tid.raw,
1634        ))) {
1635            .SUCCESS => return Impl{ .thread = &instance.thread },
1636            .AGAIN => return error.ThreadQuotaExceeded,
1637            .INVAL => unreachable,
1638            .NOMEM => return error.SystemResources,
1639            .NOSPC => unreachable,
1640            .PERM => unreachable,
1641            .USERS => unreachable,
1642            else => |err| return posix.unexpectedErrno(err),
1643        }
1644    }
1645
1646    fn getHandle(self: Impl) ThreadHandle {
1647        return self.thread.parent_tid;
1648    }
1649
1650    fn detach(self: Impl) void {
1651        switch (self.thread.completion.swap(.detached, .seq_cst)) {
1652            .running => {},
1653            .completed => self.join(),
1654            .detached => unreachable,
1655        }
1656    }
1657
1658    fn join(self: Impl) void {
1659        defer posix.munmap(self.thread.mapped);
1660
1661        while (true) {
1662            const tid = self.thread.child_tid.load(.seq_cst);
1663            if (tid == 0) break;
1664
1665            switch (linux.errno(linux.futex_4arg(
1666                &self.thread.child_tid.raw,
1667                .{ .cmd = .WAIT, .private = false },
1668                @bitCast(tid),
1669                null,
1670            ))) {
1671                .SUCCESS => continue,
1672                .INTR => continue,
1673                .AGAIN => continue,
1674                else => unreachable,
1675            }
1676        }
1677    }
1678};
1679
1680fn testThreadName(thread: *Thread) !void {
1681    const testCases = &[_][]const u8{
1682        "mythread",
1683        "b" ** max_name_len,
1684    };
1685
1686    inline for (testCases) |tc| {
1687        try thread.setName(tc);
1688
1689        var name_buffer: [max_name_len:0]u8 = undefined;
1690
1691        const name = try thread.getName(&name_buffer);
1692        if (name) |value| {
1693            try std.testing.expectEqual(tc.len, value.len);
1694            try std.testing.expectEqualStrings(tc, value);
1695        }
1696    }
1697}
1698
1699test "setName, getName" {
1700    if (builtin.single_threaded) return error.SkipZigTest;
1701
1702    const Context = struct {
1703        start_wait_event: ResetEvent = .unset,
1704        test_done_event: ResetEvent = .unset,
1705        thread_done_event: ResetEvent = .unset,
1706
1707        done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
1708        thread: Thread = undefined,
1709
1710        pub fn run(ctx: *@This()) !void {
1711            // Wait for the main thread to have set the thread field in the context.
1712            ctx.start_wait_event.wait();
1713
1714            switch (native_os) {
1715                .windows => testThreadName(&ctx.thread) catch |err| switch (err) {
1716                    error.Unsupported => return error.SkipZigTest,
1717                    else => return err,
1718                },
1719                else => try testThreadName(&ctx.thread),
1720            }
1721
1722            // Signal our test is done
1723            ctx.test_done_event.set();
1724
1725            // wait for the thread to property exit
1726            ctx.thread_done_event.wait();
1727        }
1728    };
1729
1730    var context = Context{};
1731    var thread = try spawn(.{}, Context.run, .{&context});
1732
1733    context.thread = thread;
1734    context.start_wait_event.set();
1735    context.test_done_event.wait();
1736
1737    switch (native_os) {
1738        .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1739            const res = thread.setName("foobar");
1740            try std.testing.expectError(error.Unsupported, res);
1741        },
1742        .windows => testThreadName(&thread) catch |err| switch (err) {
1743            error.Unsupported => return error.SkipZigTest,
1744            else => return err,
1745        },
1746        else => try testThreadName(&thread),
1747    }
1748
1749    context.thread_done_event.set();
1750    thread.join();
1751}
1752
1753test {
1754    _ = Futex;
1755    _ = ResetEvent;
1756    _ = Mutex;
1757    _ = Semaphore;
1758    _ = Condition;
1759    _ = RwLock;
1760    _ = Pool;
1761}
1762
1763fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
1764    value.* += 1;
1765    event.set();
1766}
1767
1768test join {
1769    if (builtin.single_threaded) return error.SkipZigTest;
1770
1771    var value: usize = 0;
1772    var event: ResetEvent = .unset;
1773
1774    const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
1775    thread.join();
1776
1777    try std.testing.expectEqual(value, 1);
1778}
1779
1780test detach {
1781    if (builtin.single_threaded) return error.SkipZigTest;
1782
1783    var value: usize = 0;
1784    var event: ResetEvent = .unset;
1785
1786    const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
1787    thread.detach();
1788
1789    event.wait();
1790    try std.testing.expectEqual(value, 1);
1791}
1792
1793test "Thread.getCpuCount" {
1794    if (native_os == .wasi) return error.SkipZigTest;
1795
1796    const cpu_count = try Thread.getCpuCount();
1797    try std.testing.expect(cpu_count >= 1);
1798}
1799
1800fn testThreadIdFn(thread_id: *Thread.Id) void {
1801    thread_id.* = Thread.getCurrentId();
1802}
1803
1804test "Thread.getCurrentId" {
1805    if (builtin.single_threaded) return error.SkipZigTest;
1806
1807    var thread_current_id: Thread.Id = undefined;
1808    const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
1809    thread.join();
1810    try std.testing.expect(Thread.getCurrentId() != thread_current_id);
1811}
1812
1813test "thread local storage" {
1814    if (builtin.single_threaded) return error.SkipZigTest;
1815    if (@sizeOf(usize) == 4) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25498
1816
1817    const thread1 = try Thread.spawn(.{}, testTls, .{});
1818    const thread2 = try Thread.spawn(.{}, testTls, .{});
1819    try testTls();
1820    thread1.join();
1821    thread2.join();
1822}
1823
1824threadlocal var x: i32 = 1234;
1825fn testTls() !void {
1826    if (x != 1234) return error.TlsBadStartValue;
1827    x += 1;
1828    if (x != 1235) return error.TlsBadEndValue;
1829}
1830
1831test "ResetEvent smoke test" {
1832    var event: ResetEvent = .unset;
1833    try testing.expectEqual(false, event.isSet());
1834
1835    // make sure the event gets set
1836    event.set();
1837    try testing.expectEqual(true, event.isSet());
1838
1839    // make sure the event gets unset again
1840    event.reset();
1841    try testing.expectEqual(false, event.isSet());
1842
1843    // waits should timeout as there's no other thread to set the event
1844    try testing.expectError(error.Timeout, event.timedWait(0));
1845    try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms));
1846
1847    // set the event again and make sure waits complete
1848    event.set();
1849    event.wait();
1850    try event.timedWait(std.time.ns_per_ms);
1851    try testing.expectEqual(true, event.isSet());
1852}
1853
1854test "ResetEvent signaling" {
1855    // This test requires spawning threads
1856    if (builtin.single_threaded) {
1857        return error.SkipZigTest;
1858    }
1859
1860    const Context = struct {
1861        in: ResetEvent = .unset,
1862        out: ResetEvent = .unset,
1863        value: usize = 0,
1864
1865        fn input(self: *@This()) !void {
1866            // wait for the value to become 1
1867            self.in.wait();
1868            self.in.reset();
1869            try testing.expectEqual(self.value, 1);
1870
1871            // bump the value and wake up output()
1872            self.value = 2;
1873            self.out.set();
1874
1875            // wait for output to receive 2, bump the value and wake us up with 3
1876            self.in.wait();
1877            self.in.reset();
1878            try testing.expectEqual(self.value, 3);
1879
1880            // bump the value and wake up output() for it to see 4
1881            self.value = 4;
1882            self.out.set();
1883        }
1884
1885        fn output(self: *@This()) !void {
1886            // start with 0 and bump the value for input to see 1
1887            try testing.expectEqual(self.value, 0);
1888            self.value = 1;
1889            self.in.set();
1890
1891            // wait for input to receive 1, bump the value to 2 and wake us up
1892            self.out.wait();
1893            self.out.reset();
1894            try testing.expectEqual(self.value, 2);
1895
1896            // bump the value to 3 for input to see (rhymes)
1897            self.value = 3;
1898            self.in.set();
1899
1900            // wait for input to bump the value to 4 and receive no more (rhymes)
1901            self.out.wait();
1902            self.out.reset();
1903            try testing.expectEqual(self.value, 4);
1904        }
1905    };
1906
1907    var ctx = Context{};
1908
1909    const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx});
1910    defer thread.join();
1911
1912    try ctx.input();
1913}
1914
1915test "ResetEvent broadcast" {
1916    // This test requires spawning threads
1917    if (builtin.single_threaded) {
1918        return error.SkipZigTest;
1919    }
1920
1921    const num_threads = 10;
1922    const Barrier = struct {
1923        event: ResetEvent = .unset,
1924        counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads),
1925
1926        fn wait(self: *@This()) void {
1927            if (self.counter.fetchSub(1, .acq_rel) == 1) {
1928                self.event.set();
1929            }
1930        }
1931    };
1932
1933    const Context = struct {
1934        start_barrier: Barrier = .{},
1935        finish_barrier: Barrier = .{},
1936
1937        fn run(self: *@This()) void {
1938            self.start_barrier.wait();
1939            self.finish_barrier.wait();
1940        }
1941    };
1942
1943    var ctx = Context{};
1944    var threads: [num_threads - 1]std.Thread = undefined;
1945
1946    for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx});
1947    defer for (threads) |t| t.join();
1948
1949    ctx.run();
1950}