authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-23 20:55:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:51-07:00
logae86c0f529d720ed4cae9e5f99064d6dba635144
treefd86ffeb2568f1aaa3e3976b1b7729ea8ed54300
parentecdc00466c34424ec8467d05efe0421f868c739a

std.Io: adjust concurrent error set

Now std.Io.Threaded can return error.ConcurrencyUnavailable rather than asserting. This is handy for logic that wants to try a concurrent implementation but then fall back to a synchronous one.

4 files changed, 23 insertions(+), 13 deletions(-)

lib/std/Io.zig+10-2
...@@ -552,6 +552,8 @@ test {...@@ -552,6 +552,8 @@ test {
552 _ = Reader;552 _ = Reader;
553 _ = Writer;553 _ = Writer;
554 _ = tty;554 _ = tty;
555 _ = Evented;
556 _ = Threaded;
555 _ = @import("Io/test.zig");557 _ = @import("Io/test.zig");
556}558}
557559
...@@ -596,7 +598,7 @@ pub const VTable = struct {...@@ -596,7 +598,7 @@ pub const VTable = struct {
596 context: []const u8,598 context: []const u8,
597 context_alignment: std.mem.Alignment,599 context_alignment: std.mem.Alignment,
598 start: *const fn (context: *const anyopaque, result: *anyopaque) void,600 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
599 ) error{OutOfMemory}!*AnyFuture,601 ) ConcurrentError!*AnyFuture,
600 /// This function is only called when `async` returns a non-null value.602 /// This function is only called when `async` returns a non-null value.
601 ///603 ///
602 /// Thread-safe.604 /// Thread-safe.
...@@ -1557,6 +1559,12 @@ pub fn async(...@@ -1557,6 +1559,12 @@ pub fn async(
1557 return future;1559 return future;
1558}1560}
15591561
1562pub const ConcurrentError = error{
1563 /// May occur due to a temporary condition such as resource exhaustion, or
1564 /// to the Io implementation not supporting concurrency.
1565 ConcurrencyUnavailable,
1566};
1567
1560/// Calls `function` with `args`, such that the return value of the function is1568/// Calls `function` with `args`, such that the return value of the function is
1561/// not guaranteed to be available until `await` is called, allowing the caller1569/// not guaranteed to be available until `await` is called, allowing the caller
1562/// to progress while waiting for any `Io` operations.1570/// to progress while waiting for any `Io` operations.
...@@ -1568,7 +1576,7 @@ pub fn concurrent(...@@ -1568,7 +1576,7 @@ pub fn concurrent(
1568 io: Io,1576 io: Io,
1569 function: anytype,1577 function: anytype,
1570 args: std.meta.ArgsTuple(@TypeOf(function)),1578 args: std.meta.ArgsTuple(@TypeOf(function)),
1571) error{OutOfMemory}!Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {1579) ConcurrentError!Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {
1572 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;1580 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;
1573 const Args = @TypeOf(args);1581 const Args = @TypeOf(args);
1574 const TypeErased = struct {1582 const TypeErased = struct {
lib/std/Io/IoUring.zig+1-1
...@@ -866,7 +866,7 @@ fn concurrent(...@@ -866,7 +866,7 @@ fn concurrent(
866 context: []const u8,866 context: []const u8,
867 context_alignment: Alignment,867 context_alignment: Alignment,
868 start: *const fn (context: *const anyopaque, result: *anyopaque) void,868 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
869) error{OutOfMemory}!*std.Io.AnyFuture {869) Io.ConcurrentError!*std.Io.AnyFuture {
870 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO870 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO
871 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO871 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
872 assert(result_len <= Fiber.max_result_size); // TODO872 assert(result_len <= Fiber.max_result_size); // TODO
lib/std/Io/Kqueue.zig+2-2
...@@ -933,14 +933,14 @@ fn concurrent(...@@ -933,14 +933,14 @@ fn concurrent(
933 context: []const u8,933 context: []const u8,
934 context_alignment: Alignment,934 context_alignment: Alignment,
935 start: *const fn (context: *const anyopaque, result: *anyopaque) void,935 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
936) error{OutOfMemory}!*Io.AnyFuture {936) Io.ConcurrentError!*Io.AnyFuture {
937 const k: *Kqueue = @ptrCast(@alignCast(userdata));937 const k: *Kqueue = @ptrCast(@alignCast(userdata));
938 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO938 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO
939 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO939 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
940 assert(result_len <= Fiber.max_result_size); // TODO940 assert(result_len <= Fiber.max_result_size); // TODO
941 assert(context.len <= Fiber.max_context_size); // TODO941 assert(context.len <= Fiber.max_context_size); // TODO
942942
943 const fiber = try Fiber.allocate(k);943 const fiber = Fiber.allocate(k) catch return error.ConcurrencyUnavailable;
944 std.log.debug("allocated {*}", .{fiber});944 std.log.debug("allocated {*}", .{fiber});
945945
946 const closure: *AsyncClosure = .fromFiber(fiber);946 const closure: *AsyncClosure = .fromFiber(fiber);
lib/std/Io/Threaded.zig+10-8
...@@ -91,9 +91,9 @@ pub fn init(...@@ -91,9 +91,9 @@ pub fn init(
91 return t;91 return t;
92}92}
9393
94/// Statically initialize such that any call to the following functions will94/// Statically initialize such that calls to `Io.VTable.concurrent` will fail
95/// fail with `error.OutOfMemory`:95/// with `error.ConcurrencyUnavailable`.
96/// * `Io.VTable.concurrent`96///
97/// When initialized this way, `deinit` is safe, but unnecessary to call.97/// When initialized this way, `deinit` is safe, but unnecessary to call.
98pub const init_single_threaded: Threaded = .{98pub const init_single_threaded: Threaded = .{
99 .allocator = .failing,99 .allocator = .failing,
...@@ -481,8 +481,8 @@ fn concurrent(...@@ -481,8 +481,8 @@ fn concurrent(
481 context: []const u8,481 context: []const u8,
482 context_alignment: std.mem.Alignment,482 context_alignment: std.mem.Alignment,
483 start: *const fn (context: *const anyopaque, result: *anyopaque) void,483 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
484) error{OutOfMemory}!*Io.AnyFuture {484) Io.ConcurrentError!*Io.AnyFuture {
485 if (builtin.single_threaded) unreachable;485 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
486486
487 const t: *Threaded = @ptrCast(@alignCast(userdata));487 const t: *Threaded = @ptrCast(@alignCast(userdata));
488 const cpu_count = t.cpu_count catch 1;488 const cpu_count = t.cpu_count catch 1;
...@@ -490,7 +490,9 @@ fn concurrent(...@@ -490,7 +490,9 @@ fn concurrent(
490 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));490 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
491 const result_offset = result_alignment.forward(context_offset + context.len);491 const result_offset = result_alignment.forward(context_offset + context.len);
492 const n = result_offset + result_len;492 const n = result_offset + result_len;
493 const ac: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));493 const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch
494 return error.ConcurrencyUnavailable;
495 const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
494496
495 ac.* = .{497 ac.* = .{
496 .closure = .{498 .closure = .{
...@@ -515,7 +517,7 @@ fn concurrent(...@@ -515,7 +517,7 @@ fn concurrent(
515 t.threads.ensureTotalCapacity(gpa, thread_capacity) catch {517 t.threads.ensureTotalCapacity(gpa, thread_capacity) catch {
516 t.mutex.unlock();518 t.mutex.unlock();
517 ac.free(gpa, result_len);519 ac.free(gpa, result_len);
518 return error.OutOfMemory;520 return error.ConcurrencyUnavailable;
519 };521 };
520522
521 t.run_queue.prepend(&ac.closure.node);523 t.run_queue.prepend(&ac.closure.node);
...@@ -525,7 +527,7 @@ fn concurrent(...@@ -525,7 +527,7 @@ fn concurrent(
525 assert(t.run_queue.popFirst() == &ac.closure.node);527 assert(t.run_queue.popFirst() == &ac.closure.node);
526 t.mutex.unlock();528 t.mutex.unlock();
527 ac.free(gpa, result_len);529 ac.free(gpa, result_len);
528 return error.OutOfMemory;530 return error.ConcurrencyUnavailable;
529 };531 };
530 t.threads.appendAssumeCapacity(thread);532 t.threads.appendAssumeCapacity(thread);
531 }533 }