Merge pull request 'bsd-futex' (#30626) from mikdusan/zig:bsd-futex into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30626
Reviewed-by: Andrew Kelley <andrewrk@noreply.codeberg.org>
This commit is contained in:
Andrew Kelley 2025-12-30 20:24:00 +01:00
commit 1eb8fe7c61
6 changed files with 97 additions and 11 deletions

View file

@ -66,13 +66,13 @@ pub fn main() void {
}
if (listen) {
return mainServer() catch @panic("internal test runner failure");
return mainServer(args) catch @panic("internal test runner failure");
} else {
return mainTerminal();
return mainTerminal(args);
}
}
fn mainServer() !void {
fn mainServer(args: []const [:0]const u8) !void {
@disableInstrumentation();
var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
@ -131,7 +131,9 @@ fn mainServer() !void {
.run_test => {
testing.allocator_instance = .{};
testing.io_instance = .init(testing.allocator, .{});
testing.io_instance = .init(testing.allocator, .{
.argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
});
log_err_count = 0;
const index = try server.receiveBody_u32();
const test_fn = builtin.test_functions[index];
@ -215,7 +217,7 @@ fn mainServer() !void {
}
}
fn mainTerminal() void {
fn mainTerminal(args: []const [:0]const u8) void {
@disableInstrumentation();
if (builtin.fuzz) @panic("fuzz test requires server");
@ -233,7 +235,9 @@ fn mainTerminal() void {
var leaks: usize = 0;
for (test_fn_list, 0..) |test_fn, i| {
testing.allocator_instance = .{};
testing.io_instance = .init(testing.allocator, .{});
testing.io_instance = .init(testing.allocator, .{
.argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
});
defer {
testing.io_instance.deinit();
if (testing.allocator_instance.deinit() == .leak) leaks += 1;

View file

@ -113,6 +113,7 @@ pub const Reader = struct {
},
.wasi => @sizeOf(std.os.wasi.dirent_t) +
std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),
.openbsd => std.c.S.BLKSIZE,
else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
};

View file

@ -390,6 +390,52 @@ const Thread = struct {
else => unreachable,
};
},
.openbsd => {
var tm: std.c.timespec = undefined;
var tm_ptr: ?*const std.c.timespec = null;
if (timeout_ns) |ns| {
tm_ptr = &tm;
tm = timestampToPosix(ns);
}
if (thread) |t| try t.beginSyscall();
const rc = std.c.futex(
ptr,
std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
@as(c_int, @bitCast(expect)),
tm_ptr,
null, // uaddr2 is ignored
);
if (thread) |t| t.endSyscall();
if (is_debug) switch (posix.errno(rc)) {
.SUCCESS => {},
.NOSYS => unreachable, // constant op known good value
.AGAIN => {}, // contents of uaddr != val
.INVAL => unreachable, // invalid timeout
.TIMEDOUT => {}, // timeout
.INTR => {}, // a signal arrived
.CANCELED => {}, // a signal arrived and SA_RESTART was set
else => unreachable,
};
},
.dragonfly => {
var timeout_us: c_int = undefined;
if (timeout_ns) |ns| {
timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int);
} else {
timeout_us = 0;
}
if (thread) |t| try t.beginSyscall();
const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
if (thread) |t| t.endSyscall();
if (is_debug) switch (std.posix.errno(rc)) {
.SUCCESS => {},
.BUSY => {}, // ptr != expect
.AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh
.INTR => {}, // spurious wake
.INVAL => unreachable, // invalid timeout
else => unreachable,
};
},
else => if (std.Thread.use_pthreads) {
// TODO integrate the following function being called with robust cancelation.
return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
@ -473,6 +519,23 @@ const Thread = struct {
else => unreachable, // deadlock due to operating system bug
}
},
.openbsd => {
const rc = std.c.futex(
ptr,
std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG,
@min(max_waiters, std.math.maxInt(c_int)),
null, // timeout is ignored
null, // uaddr2 is ignored
);
assert(rc >= 0);
},
.dragonfly => {
// will generally return 0 unless the address is bad
_ = std.c.umtx_wakeup(
@ptrCast(ptr),
@min(max_waiters, std.math.maxInt(c_int)),
);
},
else => if (std.Thread.use_pthreads) {
return pthreads_futex.wake(ptr, max_waiters);
} else {

View file

@ -167,6 +167,18 @@ pub const timespec = switch (native_os) {
.openbsd, .haiku => extern struct {
sec: time_t,
nsec: isize,
/// For use with `utimensat` and `futimens`.
pub const NOW: timespec = .{
.sec = 0, // ignored
.nsec = -2,
};
/// For use with `utimensat` and `futimens`.
pub const OMIT: timespec = .{
.sec = 0, // ignored
.nsec = -1,
};
},
else => void,
};
@ -2365,6 +2377,8 @@ pub const S = switch (native_os) {
pub const IWOTH = 0o002;
pub const IXOTH = 0o001;
pub const BLKSIZE = 512;
pub fn ISFIFO(m: u32) bool {
return m & IFMT == IFIFO;
}
@ -9676,6 +9690,7 @@ pub const NSIG = switch (native_os) {
.illumos => 75,
// https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L42
.openbsd, .serenity => 33,
.dragonfly => 64,
else => {},
};

View file

@ -645,6 +645,7 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo
test "Dir.realPath smoke test" {
if (native_os == .wasi) return error.SkipZigTest;
if (native_os == .openbsd) return error.SkipZigTest;
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
@ -820,7 +821,7 @@ test "file operations on directories" {
try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{}));
try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name));
switch (native_os) {
.dragonfly, .netbsd => {
.netbsd => {
// no error when reading a directory. See https://github.com/ziglang/zig/issues/5732
const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);
testing.allocator.free(buf);
@ -1240,6 +1241,7 @@ test "createDirPath, put some files in it, deleteTreeMinStackSize" {
test "createDirPath in a directory that no longer exists" {
if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
if (native_os == .dragonfly) return error.SkipZigTest; // DragonflyBSD does not produce error (hammer2 fs)
const io = testing.io;

View file

@ -364,9 +364,10 @@ test "getrlimit and setrlimit" {
}
test "sigrtmin/max" {
if (native_os == .wasi or native_os == .windows or native_os.isDarwin() or native_os == .openbsd) {
return error.SkipZigTest;
}
if (native_os.isDarwin() or switch (native_os) {
.wasi, .windows, .openbsd, .dragonfly => true,
else => false,
}) return error.SkipZigTest;
try expect(posix.sigrtmin() >= 32);
try expect(posix.sigrtmin() >= posix.system.sigrtmin());
@ -397,7 +398,7 @@ fn reserved_signo(i: usize) bool {
if (!builtin.link_libc) return false;
const max = if (native_os == .netbsd) 32 else 31;
if (i > max) return true;
if (native_os == .openbsd) return false; // no RT signals
if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals
return i < posix.sigrtmin();
}