| author | |
| committer | |
| log | ae86c0f529d720ed4cae9e5f99064d6dba635144 |
| tree | fd86ffeb2568f1aaa3e3976b1b7729ea8ed54300 |
| parent | ecdc00466c34424ec8467d05efe0421f868c739a |
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 | } |
| 557 | 559 | ||
| ... | @@ -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 | } |
| 1559 | 1561 | ||
| 1562 | pub 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 is | 1568 | /// 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 caller | 1569 | /// 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)); // TODO | 870 | assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO |
| 871 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | 871 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO |
| 872 | assert(result_len <= Fiber.max_result_size); // TODO | 872 | 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)); // TODO | 938 | assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO |
| 939 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | 939 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO |
| 940 | assert(result_len <= Fiber.max_result_size); // TODO | 940 | assert(result_len <= Fiber.max_result_size); // TODO |
| 941 | assert(context.len <= Fiber.max_context_size); // TODO | 941 | assert(context.len <= Fiber.max_context_size); // TODO |
| 942 | 942 | ||
| 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}); |
| 945 | 945 | ||
| 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 | } |
| 93 | 93 | ||
| 94 | /// Statically initialize such that any call to the following functions will | 94 | /// 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. |
| 98 | pub const init_single_threaded: Threaded = .{ | 98 | pub 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; |
| 486 | 486 | ||
| 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)); | ||
| 494 | 496 | ||
| 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 | }; |
| 520 | 522 | ||
| 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 | } |