| author | |
| committer | |
| log | 642f329ac91d69d02588ba15714edafb09e709da |
| tree | a56b07262a9a804b7dd450cb4ead04c81c9c5df2 |
| parent | e2a266e744adaa7ee84a512fdf4cea44686ca0b6 |
5 files changed, 312 insertions(+), 152 deletions(-)
lib/std/Io.zig+84-11| ... | @@ -149,7 +149,10 @@ pub const VTable = struct { | ... | @@ -149,7 +149,10 @@ pub const VTable = struct { |
| 149 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, | 149 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 150 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, | 150 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 151 | 151 | ||
| 152 | operate: *const fn (?*anyopaque, []Operation) void, | 152 | batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, |
| 153 | batchSubmit: *const fn (?*anyopaque, *Batch) void, | ||
| 154 | batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, | ||
| 155 | batchCancel: *const fn (?*anyopaque, *Batch) void, | ||
| 153 | 156 | ||
| 154 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, | 157 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| 155 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, | 158 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, |
| ... | @@ -253,26 +256,96 @@ pub const VTable = struct { | ... | @@ -253,26 +256,96 @@ pub const VTable = struct { |
| 253 | }; | 256 | }; |
| 254 | 257 | ||
| 255 | pub const Operation = union(enum) { | 258 | pub const Operation = union(enum) { |
| 256 | noop, | 259 | noop: Noop, |
| 257 | file_read_streaming: FileReadStreaming, | 260 | file_read_streaming: FileReadStreaming, |
| 258 | 261 | ||
| 262 | pub const Noop = struct { | ||
| 263 | reserved: [2]usize, | ||
| 264 | status: Status(void) = .{ .result = {} }, | ||
| 265 | }; | ||
| 266 | |||
| 267 | /// Returns 0 on end of stream. | ||
| 259 | pub const FileReadStreaming = struct { | 268 | pub const FileReadStreaming = struct { |
| 260 | file: File, | 269 | file: File, |
| 261 | data: []const []u8, | 270 | data: []const []u8, |
| 262 | /// Causes `result` to return `error.WouldBlock` instead of blocking. | 271 | status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, |
| 263 | nonblocking: bool = false, | ||
| 264 | /// Returns 0 on end of stream. | ||
| 265 | result: File.Reader.Error!usize, | ||
| 266 | }; | 272 | }; |
| 273 | |||
| 274 | pub fn Status(Result: type) type { | ||
| 275 | return union { | ||
| 276 | unstarted: void, | ||
| 277 | pending: usize, | ||
| 278 | result: Result, | ||
| 279 | }; | ||
| 280 | } | ||
| 267 | }; | 281 | }; |
| 268 | 282 | ||
| 269 | /// Performs all `operations` in a non-deterministic order. Returns after all | 283 | /// Performs all `operations` in an unspecified order, concurrently. |
| 270 | /// `operations` have been completed. The degree to which the operations are | 284 | /// |
| 271 | /// performed concurrently is determined by the `Io` implementation. | 285 | /// Returns after all `operations` have been completed. If the operations could |
| 272 | pub fn operate(io: Io, operations: []Operation) void { | 286 | /// not be completed concurrently, returns `error.ConcurrencyUnavailable`. |
| 273 | return io.vtable.operate(io.userdata, operations); | 287 | /// |
| 288 | /// With this API, it is rare for concurrency to not be available. Even a | ||
| 289 | /// single-threaded `Io` implementation can, for example, take advantage of | ||
| 290 | /// poll() to implement this. Note that poll() is fallible however. | ||
| 291 | /// | ||
| 292 | /// If `operations.len` is one, `error.ConcurrencyUnavailable` is unreachable. | ||
| 293 | /// | ||
| 294 | /// On entry, all operations must already have `.status = .unstarted` except | ||
| 295 | /// noops must have `.status = .{ .result = {} }`, to safety check the state | ||
| 296 | /// transitions. | ||
| 297 | /// | ||
| 298 | /// On return, all operations have `.status = .{ .result = ... }`. | ||
| 299 | pub fn batch(io: Io, operations: []Operation) ConcurrentError!void { | ||
| 300 | return io.vtable.batch(io.userdata, operations); | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Performs one `Operation`. | ||
| 304 | pub fn operate(io: Io, operation: *Operation) void { | ||
| 305 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | ||
| 274 | } | 306 | } |
| 275 | 307 | ||
| 308 | /// Submits many operations together without waiting for all of them to | ||
| 309 | /// complete. | ||
| 310 | /// | ||
| 311 | /// This is a low-level abstraction based on `Operation`. For a higher | ||
| 312 | /// level API that operates on `Future`, see `Select`. | ||
| 313 | pub const Batch = struct { | ||
| 314 | operations: []Operation, | ||
| 315 | index: usize, | ||
| 316 | reserved: ?*anyopaque, | ||
| 317 | |||
| 318 | pub fn init(operations: []Operation) Batch { | ||
| 319 | return .{ .operations = operations, .index = 0, .reserved = null }; | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Submits all non-noop `operations`. | ||
| 323 | pub fn submit(b: *Batch, io: Io) void { | ||
| 324 | return io.vtable.batchSubmit(io.userdata, b); | ||
| 325 | } | ||
| 326 | |||
| 327 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | ||
| 328 | |||
| 329 | /// Resubmits the previously completed or noop-initialized `operations` at | ||
| 330 | /// indexes given by `resubmissions`. This set of indexes typically will be empty | ||
| 331 | /// on the first call to `await` since all operations have already been | ||
| 332 | /// submitted via `async`. | ||
| 333 | /// | ||
| 334 | /// Returns the index of a completed `Operation`, or `operations.len` if | ||
| 335 | /// all operations are completed. | ||
| 336 | /// | ||
| 337 | /// When `error.Canceled` is returned, all operations have already completed. | ||
| 338 | pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { | ||
| 339 | return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); | ||
| 340 | } | ||
| 341 | |||
| 342 | /// Returns after all `operations` have completed. Each operation | ||
| 343 | /// independently may or may not have been canceled. | ||
| 344 | pub fn cancel(b: *Batch, io: Io) void { | ||
| 345 | return io.vtable.batchCancel(io.userdata, b); | ||
| 346 | } | ||
| 347 | }; | ||
| 348 | |||
| 276 | pub const Limit = enum(usize) { | 349 | pub const Limit = enum(usize) { |
| 277 | nothing = 0, | 350 | nothing = 0, |
| 278 | unlimited = math.maxInt(usize), | 351 | unlimited = math.maxInt(usize), |
lib/std/Io/File.zig+2-3| ... | @@ -557,10 +557,9 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz | ... | @@ -557,10 +557,9 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 557 | var operation: Io.Operation = .{ .file_read_streaming = .{ | 557 | var operation: Io.Operation = .{ .file_read_streaming = .{ |
| 558 | .file = file, | 558 | .file = file, |
| 559 | .data = buffer, | 559 | .data = buffer, |
| 560 | .result = undefined, | ||
| 561 | } }; | 560 | } }; |
| 562 | io.vtable.operate(io.userdata, (&operation)[0..1]); | 561 | io.operate(&operation); |
| 563 | return operation.file_read_streaming.result; | 562 | return operation.file_read_streaming.status.result; |
| 564 | } | 563 | } |
| 565 | 564 | ||
| 566 | pub const ReadPositionalError = error{ | 565 | pub const ReadPositionalError = error{ |
lib/std/Io/Threaded.zig+171-85| ... | @@ -1587,7 +1587,10 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1587,7 +1587,10 @@ pub fn io(t: *Threaded) Io { |
| 1587 | .futexWaitUncancelable = futexWaitUncancelable, | 1587 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1588 | .futexWake = futexWake, | 1588 | .futexWake = futexWake, |
| 1589 | 1589 | ||
| 1590 | .operate = operate, | 1590 | .batch = batch, |
| 1591 | .batchSubmit = batchSubmit, | ||
| 1592 | .batchWait = batchWait, | ||
| 1593 | .batchCancel = batchCancel, | ||
| 1591 | 1594 | ||
| 1592 | .dirCreateDir = dirCreateDir, | 1595 | .dirCreateDir = dirCreateDir, |
| 1593 | .dirCreateDirPath = dirCreateDirPath, | 1596 | .dirCreateDirPath = dirCreateDirPath, |
| ... | @@ -1748,7 +1751,10 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1748,7 +1751,10 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1748 | .futexWaitUncancelable = futexWaitUncancelable, | 1751 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1749 | .futexWake = futexWake, | 1752 | .futexWake = futexWake, |
| 1750 | 1753 | ||
| 1751 | .operate = operate, | 1754 | .batch = batch, |
| 1755 | .batchSubmit = batchSubmit, | ||
| 1756 | .batchWait = batchWait, | ||
| 1757 | .batchCancel = batchCancel, | ||
| 1752 | 1758 | ||
| 1753 | .dirCreateDir = dirCreateDir, | 1759 | .dirCreateDir = dirCreateDir, |
| 1754 | .dirCreateDirPath = dirCreateDirPath, | 1760 | .dirCreateDirPath = dirCreateDirPath, |
| ... | @@ -2450,107 +2456,187 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -2450,107 +2456,187 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2450 | Thread.futexWake(ptr, max_waiters); | 2456 | Thread.futexWake(ptr, max_waiters); |
| 2451 | } | 2457 | } |
| 2452 | 2458 | ||
| 2453 | fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { | 2459 | fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2454 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2460 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2455 | _ = t; | 2461 | _ = t; |
| 2462 | _ = b; | ||
| 2463 | return; | ||
| 2464 | } | ||
| 2465 | |||
| 2466 | fn operate(op: *Io.Operation) void { | ||
| 2467 | switch (op.*) { | ||
| 2468 | .noop => {}, | ||
| 2469 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | ||
| 2470 | } | ||
| 2471 | } | ||
| 2456 | 2472 | ||
| 2473 | fn batchWait( | ||
| 2474 | userdata: ?*anyopaque, | ||
| 2475 | b: *Io.Batch, | ||
| 2476 | resubmissions: []const usize, | ||
| 2477 | timeout: Io.Timeout, | ||
| 2478 | ) Io.Batch.WaitError!usize { | ||
| 2479 | _ = resubmissions; | ||
| 2480 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 2481 | const operations = b.operations; | ||
| 2482 | if (operations.len == 1) { | ||
| 2483 | operate(&operations[0]); | ||
| 2484 | return b.operations.len; | ||
| 2485 | } | ||
| 2457 | if (is_windows) @panic("TODO"); | 2486 | if (is_windows) @panic("TODO"); |
| 2458 | 2487 | ||
| 2459 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | 2488 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2460 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | 2489 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index |
| 2461 | var operation_index: usize = 0; | 2490 | var poll_i: usize = 0; |
| 2462 | 2491 | ||
| 2463 | while (operation_index < operations.len) { | 2492 | for (operations, 0..) |*op, operation_index| switch (op.*) { |
| 2464 | var poll_i: usize = 0; | 2493 | .noop => continue, |
| 2465 | while (operation_index < operations.len) : (operation_index += 1) { | 2494 | .file_read_streaming => |*o| { |
| 2466 | switch (operations[operation_index]) { | 2495 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; |
| 2467 | .noop => continue, | 2496 | poll_buffer[poll_i] = .{ |
| 2468 | .file_read_streaming => |*o| { | 2497 | .fd = o.file.handle, |
| 2469 | if (o.nonblocking) { | 2498 | .events = posix.POLL.IN, |
| 2470 | o.result = error.WouldBlock; | 2499 | .revents = 0, |
| 2471 | poll_buffer[poll_i] = .{ | 2500 | }; |
| 2472 | .fd = o.file.handle, | 2501 | map_buffer[poll_i] = @intCast(operation_index); |
| 2473 | .events = posix.POLL.IN, | 2502 | poll_i += 1; |
| 2474 | .revents = 0, | 2503 | }, |
| 2475 | }; | 2504 | }; |
| 2476 | if (map_buffer.len - poll_i == 0) break; | ||
| 2477 | map_buffer[poll_i] = @intCast(operation_index); | ||
| 2478 | poll_i += 1; | ||
| 2479 | } else { | ||
| 2480 | o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { | ||
| 2481 | error.Canceled => { | ||
| 2482 | setOperationsError(operations[operation_index..], error.Canceled); | ||
| 2483 | return; | ||
| 2484 | }, | ||
| 2485 | else => err, | ||
| 2486 | }; | ||
| 2487 | } | ||
| 2488 | }, | ||
| 2489 | } | ||
| 2490 | } | ||
| 2491 | 2505 | ||
| 2492 | if (poll_i == 0) { | 2506 | if (poll_i == 0) return operations.len; |
| 2493 | @branchHint(.likely); | 2507 | |
| 2494 | return; | 2508 | const t_io = ioBasic(t); |
| 2509 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; | ||
| 2510 | const max_poll_ms = std.math.maxInt(i32); | ||
| 2511 | |||
| 2512 | while (true) { | ||
| 2513 | const timeout_ms: i32 = if (deadline) |d| t: { | ||
| 2514 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; | ||
| 2515 | if (duration.raw.nanoseconds <= 0) return error.Timeout; | ||
| 2516 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); | ||
| 2517 | } else -1; | ||
| 2518 | const syscall = try Syscall.start(); | ||
| 2519 | const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); | ||
| 2520 | syscall.finish(); | ||
| 2521 | switch (posix.errno(rc)) { | ||
| 2522 | .SUCCESS => { | ||
| 2523 | if (rc == 0) { | ||
| 2524 | // Although spurious timeouts are OK, when no deadline is | ||
| 2525 | // passed we must not return `error.Timeout`. | ||
| 2526 | if (deadline == null) continue; | ||
| 2527 | return error.Timeout; | ||
| 2528 | } | ||
| 2529 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | ||
| 2530 | if (poll_fd.revents == 0) continue; | ||
| 2531 | operate(&operations[i]); | ||
| 2532 | return i; | ||
| 2533 | } | ||
| 2534 | }, | ||
| 2535 | .INTR => continue, | ||
| 2536 | else => return error.ConcurrencyUnavailable, | ||
| 2495 | } | 2537 | } |
| 2538 | } | ||
| 2539 | } | ||
| 2496 | 2540 | ||
| 2497 | while (true) { | 2541 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2498 | const syscall = Syscall.start() catch |err| switch (err) { | 2542 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2499 | error.Canceled => { | 2543 | _ = t; |
| 2500 | setPollOperationsError(operations, map_buffer[0..poll_i], error.Canceled); | 2544 | _ = b; |
| 2501 | setOperationsError(operations[operation_index..], error.Canceled); | 2545 | return; |
| 2502 | return; | 2546 | } |
| 2503 | }, | 2547 | |
| 2548 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { | ||
| 2549 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 2550 | _ = t; | ||
| 2551 | |||
| 2552 | if (operations.len == 1) { | ||
| 2553 | @branchHint(.likely); | ||
| 2554 | return operate(&operations[0]); | ||
| 2555 | } | ||
| 2556 | |||
| 2557 | if (is_windows) @panic("TODO"); | ||
| 2558 | |||
| 2559 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | ||
| 2560 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | ||
| 2561 | var poll_i: usize = 0; | ||
| 2562 | |||
| 2563 | for (operations, 0..) |*op, operation_index| switch (op.*) { | ||
| 2564 | .noop => continue, | ||
| 2565 | .file_read_streaming => |*o| { | ||
| 2566 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | ||
| 2567 | poll_buffer[poll_i] = .{ | ||
| 2568 | .fd = o.file.handle, | ||
| 2569 | .events = posix.POLL.IN, | ||
| 2570 | .revents = 0, | ||
| 2504 | }; | 2571 | }; |
| 2505 | const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); | 2572 | map_buffer[poll_i] = @intCast(operation_index); |
| 2506 | syscall.finish(); | 2573 | poll_i += 1; |
| 2507 | switch (posix.errno(poll_rc)) { | 2574 | }, |
| 2508 | .SUCCESS => { | 2575 | }; |
| 2509 | if (poll_rc == 0) { | 2576 | |
| 2510 | // Spurious timeout; handle same as INTR. | 2577 | const polls = poll_buffer[0..poll_i]; |
| 2511 | continue; | 2578 | const map = map_buffer[0..poll_i]; |
| 2512 | } | 2579 | |
| 2513 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | 2580 | var pending = poll_i; |
| 2514 | if (poll_fd.revents == 0) continue; | 2581 | while (pending > 1) { |
| 2515 | switch (operations[i]) { | 2582 | const syscall = Syscall.start() catch |err| switch (err) { |
| 2516 | .noop => unreachable, | 2583 | error.Canceled => { |
| 2517 | .file_read_streaming => |*o| { | 2584 | if (!setOperationsError(operations, polls, map, error.Canceled)) |
| 2518 | o.result = fileReadStreaming(o.file, o.data); | 2585 | recancelInner(); |
| 2519 | }, | 2586 | return; |
| 2520 | } | 2587 | }, |
| 2521 | } | 2588 | }; |
| 2522 | break; | 2589 | const rc = posix.system.poll(polls.ptr, polls.len, -1); |
| 2523 | }, | 2590 | syscall.finish(); |
| 2524 | .INTR => continue, | 2591 | switch (posix.errno(rc)) { |
| 2525 | .NOMEM => { | 2592 | .SUCCESS => { |
| 2526 | setPollOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); | 2593 | if (rc == 0) { |
| 2527 | break; | 2594 | // Spurious timeout; handle the same as INTR. |
| 2528 | }, | 2595 | continue; |
| 2529 | else => { | 2596 | } |
| 2530 | setPollOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); | 2597 | for (polls, map) |*poll_fd, i| { |
| 2531 | break; | 2598 | if (poll_fd.revents == 0) continue; |
| 2532 | }, | 2599 | poll_fd.fd = -1; |
| 2533 | } | 2600 | pending -= 1; |
| 2601 | operate(&operations[i]); | ||
| 2602 | } | ||
| 2603 | }, | ||
| 2604 | .INTR => continue, | ||
| 2605 | .NOMEM => { | ||
| 2606 | assert(setOperationsError(operations, polls, map, error.SystemResources)); | ||
| 2607 | return; | ||
| 2608 | }, | ||
| 2609 | else => { | ||
| 2610 | assert(setOperationsError(operations, polls, map, error.Unexpected)); | ||
| 2611 | return; | ||
| 2612 | }, | ||
| 2534 | } | 2613 | } |
| 2535 | } | 2614 | } |
| 2615 | |||
| 2616 | if (pending == 1) for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | ||
| 2617 | if (poll_fd.fd == -1) continue; | ||
| 2618 | operate(&operations[i]); | ||
| 2619 | }; | ||
| 2536 | } | 2620 | } |
| 2537 | 2621 | ||
| 2538 | fn setPollOperationsError( | 2622 | fn setOperationsError( |
| 2539 | operations: []Io.Operation, | 2623 | operations: []Io.Operation, |
| 2624 | polls: []const posix.pollfd, | ||
| 2540 | map: []const u8, | 2625 | map: []const u8, |
| 2541 | err: error{ Canceled, SystemResources, Unexpected }, | 2626 | err: error{ Canceled, SystemResources, Unexpected }, |
| 2542 | ) void { | 2627 | ) bool { |
| 2543 | for (map) |operation_index| switch (operations[operation_index]) { | 2628 | var marked = false; |
| 2544 | .noop => unreachable, | 2629 | for (polls, map) |*poll_fd, i| { |
| 2545 | inline else => |*o| o.result = err, | 2630 | if (poll_fd.fd == -1) continue; |
| 2546 | }; | 2631 | switch (operations[i]) { |
| 2547 | } | 2632 | .noop => unreachable, |
| 2548 | 2633 | inline else => |*o| { | |
| 2549 | fn setOperationsError(operations: []Io.Operation, err: error{ Canceled, SystemResources, Unexpected }) void { | 2634 | o.status = .{ .result = err }; |
| 2550 | for (operations) |*op| switch (op.*) { | 2635 | marked = true; |
| 2551 | .noop => unreachable, | 2636 | }, |
| 2552 | inline else => |*o| o.result = err, | 2637 | } |
| 2553 | }; | 2638 | } |
| 2639 | return marked; | ||
| 2554 | } | 2640 | } |
| 2555 | 2641 | ||
| 2556 | const dirCreateDir = switch (native_os) { | 2642 | const dirCreateDir = switch (native_os) { |
lib/std/process.zig+8-8| ... | @@ -453,9 +453,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { | ... | @@ -453,9 +453,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { |
| 453 | return io.vtable.processSpawnPath(io.userdata, dir, options); | 453 | return io.vtable.processSpawnPath(io.userdata, dir, options); |
| 454 | } | 454 | } |
| 455 | 455 | ||
| 456 | pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ | 456 | pub const RunError = SpawnError || Child.CollectOutputError; |
| 457 | StreamTooLong, | ||
| 458 | }; | ||
| 459 | 457 | ||
| 460 | pub const RunOptions = struct { | 458 | pub const RunOptions = struct { |
| 461 | argv: []const []const u8, | 459 | argv: []const []const u8, |
| ... | @@ -535,13 +533,15 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { | ... | @@ -535,13 +533,15 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { |
| 535 | 533 | ||
| 536 | const term = try child.wait(io); | 534 | const term = try child.wait(io); |
| 537 | 535 | ||
| 538 | const owned_stdout = try stdout.toOwnedSlice(gpa); | 536 | const stdout_slice = try stdout.toOwnedSlice(gpa); |
| 539 | errdefer gpa.free(owned_stdout); | 537 | errdefer gpa.free(stdout_slice); |
| 540 | const owned_stderr = try stderr.toOwnedSlice(gpa); | 538 | |
| 539 | const stderr_slice = try stderr.toOwnedSlice(gpa); | ||
| 540 | errdefer gpa.free(stderr_slice); | ||
| 541 | 541 | ||
| 542 | return .{ | 542 | return .{ |
| 543 | .stdout = owned_stdout, | 543 | .stdout = stdout_slice, |
| 544 | .stderr = owned_stderr, | 544 | .stderr = stderr_slice, |
| 545 | .term = term, | 545 | .term = term, |
| 546 | }; | 546 | }; |
| 547 | } | 547 | } |
lib/std/process/Child.zig+47-45| ... | @@ -125,7 +125,9 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { | ... | @@ -125,7 +125,9 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { |
| 125 | return io.vtable.childWait(io.userdata, child); | 125 | return io.vtable.childWait(io.userdata, child); |
| 126 | } | 126 | } |
| 127 | 127 | ||
| 128 | pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; | 128 | pub const CollectOutputError = error{ |
| 129 | StreamTooLong, | ||
| 130 | } || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; | ||
| 129 | 131 | ||
| 130 | pub const CollectOutputOptions = struct { | 132 | pub const CollectOutputOptions = struct { |
| 131 | stdout: *std.ArrayList(u8), | 133 | stdout: *std.ArrayList(u8), |
| ... | @@ -135,6 +137,7 @@ pub const CollectOutputOptions = struct { | ... | @@ -135,6 +137,7 @@ pub const CollectOutputOptions = struct { |
| 135 | allocator: ?Allocator = null, | 137 | allocator: ?Allocator = null, |
| 136 | stdout_limit: Io.Limit = .unlimited, | 138 | stdout_limit: Io.Limit = .unlimited, |
| 137 | stderr_limit: Io.Limit = .unlimited, | 139 | stderr_limit: Io.Limit = .unlimited, |
| 140 | timeout: Io.Timeout = .none, | ||
| 138 | }; | 141 | }; |
| 139 | 142 | ||
| 140 | /// Collect the output from the process's stdout and stderr. Will return once | 143 | /// Collect the output from the process's stdout and stderr. Will return once |
| ... | @@ -144,56 +147,55 @@ pub const CollectOutputOptions = struct { | ... | @@ -144,56 +147,55 @@ pub const CollectOutputOptions = struct { |
| 144 | /// The process must have been started with stdout and stderr set to | 147 | /// The process must have been started with stdout and stderr set to |
| 145 | /// `process.SpawnOptions.StdIo.pipe`. | 148 | /// `process.SpawnOptions.StdIo.pipe`. |
| 146 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { | 149 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { |
| 147 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; | ||
| 148 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; | 150 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; |
| 149 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; | 151 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; |
| 150 | var dones: [2]bool = .{ false, false }; | 152 | |
| 151 | var reads: [2]Io.Operation = undefined; | 153 | if (options.allocator) |gpa| { |
| 154 | for (lists) |list| try list.ensureUnusedCapacity(gpa, 1); | ||
| 155 | } else { | ||
| 156 | for (lists) |list| { | ||
| 157 | if (list.unusedCapacitySlice().len == 0) | ||
| 158 | return error.StreamTooLong; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 152 | var vecs: [2][1][]u8 = undefined; | 162 | var vecs: [2][1][]u8 = undefined; |
| 153 | while (true) { | 163 | for (lists, &vecs) |list, *vec| |
| 154 | for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { | 164 | vec[0] = list.unusedCapacitySlice(); |
| 155 | if (done) { | 165 | |
| 156 | read.* = .noop; | 166 | var operations: [2]Io.Operation = .{ |
| 157 | continue; | 167 | .{ .file_read_streaming = .{ |
| 158 | } | 168 | .file = child.stdout.?, |
| 169 | .data = &vecs[0], | ||
| 170 | } }, | ||
| 171 | .{ .file_read_streaming = .{ | ||
| 172 | .file = child.stderr.?, | ||
| 173 | .data = &vecs[1], | ||
| 174 | } }, | ||
| 175 | }; | ||
| 176 | |||
| 177 | var batch: Io.Batch = .init(&operations); | ||
| 178 | batch.submit(io); | ||
| 179 | defer batch.cancel(io); | ||
| 180 | |||
| 181 | var pending = operations.len; | ||
| 182 | var retry_index: ?usize = null; | ||
| 183 | while (pending > 0) { | ||
| 184 | const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{}; | ||
| 185 | const index = try batch.wait(io, resubmissions, options.timeout); | ||
| 186 | const n = try operations[index].file_read_streaming.status.result; | ||
| 187 | if (n == 0) { | ||
| 188 | pending -= 1; | ||
| 189 | } else { | ||
| 190 | retry_index = index; | ||
| 191 | const list = lists[index]; | ||
| 192 | const limit = limits[index]; | ||
| 193 | list.items.len += n; | ||
| 194 | if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong; | ||
| 159 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); | 195 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); |
| 160 | const cap = list.unusedCapacitySlice(); | 196 | const cap = list.unusedCapacitySlice(); |
| 161 | if (cap.len == 0) return error.StreamTooLong; | 197 | if (cap.len == 0) return error.StreamTooLong; |
| 162 | vec[0] = cap; | 198 | vecs[index][0] = cap; |
| 163 | read.* = .{ .file_read_streaming = .{ | ||
| 164 | .file = file, | ||
| 165 | .data = vec, | ||
| 166 | .nonblocking = true, | ||
| 167 | .result = undefined, | ||
| 168 | } }; | ||
| 169 | } | ||
| 170 | var all_done = true; | ||
| 171 | var any_canceled = false; | ||
| 172 | var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; | ||
| 173 | io.vtable.operate(io.userdata, &reads); | ||
| 174 | for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { | ||
| 175 | if (done.*) continue; | ||
| 176 | const n = read.file_read_streaming.result catch |err| switch (err) { | ||
| 177 | error.Canceled => { | ||
| 178 | any_canceled = true; | ||
| 179 | continue; | ||
| 180 | }, | ||
| 181 | error.WouldBlock => continue, | ||
| 182 | else => |e| { | ||
| 183 | other_err = e; | ||
| 184 | continue; | ||
| 185 | }, | ||
| 186 | }; | ||
| 187 | if (n == 0) { | ||
| 188 | done.* = true; | ||
| 189 | } else { | ||
| 190 | all_done = false; | ||
| 191 | } | ||
| 192 | list.items.len += n; | ||
| 193 | if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; | ||
| 194 | } | 199 | } |
| 195 | if (any_canceled) return error.Canceled; | ||
| 196 | try other_err; | ||
| 197 | if (all_done) return; | ||
| 198 | } | 200 | } |
| 199 | } | 201 | } |