| author | |
| committer | |
| log | dea653fdb97c62968138c28496f25510b5d50024 |
| tree | 037776338497fd9675c83c327ba38316cfedb66f |
| parent | 5456f953fab3254bd87a1be8d7c42b15d4bb8138 |
5 files changed, 258 insertions(+), 158 deletions(-)
lib/std/Io.zig+121-48| ... | ... | @@ -149,9 +149,8 @@ pub const VTable = struct { |
| 149 | 149 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 150 | 150 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 151 | 151 | |
| 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, | |
| 152 | operate: *const fn (?*anyopaque, *Operation) Cancelable!void, | |
| 153 | batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, | |
| 155 | 154 | batchCancel: *const fn (?*anyopaque, *Batch) void, |
| 156 | 155 | |
| 157 | 156 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| ... | ... | @@ -261,48 +260,50 @@ pub const Operation = union(enum) { |
| 261 | 260 | |
| 262 | 261 | pub const Noop = struct { |
| 263 | 262 | reserved: [2]usize = .{ 0, 0 }, |
| 264 | status: Status(void) = .{ .result = {} }, | |
| 263 | status: Status(void) = .{ .unstarted = {} }, | |
| 265 | 264 | }; |
| 266 | 265 | |
| 267 | 266 | /// Returns 0 on end of stream. |
| 268 | 267 | pub const FileReadStreaming = struct { |
| 269 | 268 | file: File, |
| 270 | 269 | data: []const []u8, |
| 271 | status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, | |
| 270 | status: Status(Error!usize) = .{ .unstarted = {} }, | |
| 271 | ||
| 272 | pub const Error = error{ | |
| 273 | InputOutput, | |
| 274 | SystemResources, | |
| 275 | /// Trying to read a directory file descriptor as if it were a file. | |
| 276 | IsDir, | |
| 277 | BrokenPipe, | |
| 278 | ConnectionResetByPeer, | |
| 279 | /// File was not opened with read capability. | |
| 280 | NotOpenForReading, | |
| 281 | SocketUnconnected, | |
| 282 | /// Non-blocking has been enabled, and reading from the file descriptor | |
| 283 | /// would block. | |
| 284 | WouldBlock, | |
| 285 | /// In WASI, this error occurs when the file descriptor does | |
| 286 | /// not hold the required rights to read from it. | |
| 287 | AccessDenied, | |
| 288 | /// Unable to read file due to lock. Depending on the `Io` implementation, | |
| 289 | /// reading from a locked file may return this error, or may ignore the | |
| 290 | /// lock. | |
| 291 | LockViolation, | |
| 292 | } || Io.UnexpectedError; | |
| 272 | 293 | }; |
| 273 | 294 | |
| 274 | 295 | pub fn Status(Result: type) type { |
| 275 | 296 | return union { |
| 276 | 297 | unstarted: void, |
| 277 | pending: usize, | |
| 298 | pending: *Batch, | |
| 278 | 299 | result: Result, |
| 279 | 300 | }; |
| 280 | 301 | } |
| 281 | 302 | }; |
| 282 | 303 | |
| 283 | /// Performs all `operations` in an unspecified order, concurrently. | |
| 284 | /// | |
| 285 | /// Returns after all `operations` have been completed. If the operations could | |
| 286 | /// not be completed concurrently, returns `error.ConcurrencyUnavailable`. | |
| 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 | 304 | /// Performs one `Operation`. |
| 304 | pub fn operate(io: Io, operation: *Operation) void { | |
| 305 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | |
| 305 | pub fn operate(io: Io, operation: *Operation) Cancelable!void { | |
| 306 | return io.vtable.operate(io.userdata, operation) catch unreachable; | |
| 306 | 307 | } |
| 307 | 308 | |
| 308 | 309 | /// Submits many operations together without waiting for all of them to |
| ... | ... | @@ -312,35 +313,107 @@ pub fn operate(io: Io, operation: *Operation) void { |
| 312 | 313 | /// level API that operates on `Future`, see `Select`. |
| 313 | 314 | pub const Batch = struct { |
| 314 | 315 | operations: []Operation, |
| 315 | index: usize, | |
| 316 | reserved: ?*anyopaque, | |
| 316 | ring: [*]u32, | |
| 317 | user: struct { | |
| 318 | submit_tail: RingIndex, | |
| 319 | complete_head: RingIndex, | |
| 320 | complete_tail: RingIndex, | |
| 321 | }, | |
| 322 | impl: struct { | |
| 323 | submit_head: RingIndex, | |
| 324 | submit_tail: RingIndex, | |
| 325 | complete_tail: RingIndex, | |
| 326 | reserved: ?*anyopaque, | |
| 327 | }, | |
| 328 | ||
| 329 | pub const RingIndex = enum(u32) { | |
| 330 | _, | |
| 331 | ||
| 332 | pub fn index(ri: RingIndex, len: u31) u31 { | |
| 333 | const i = @intFromEnum(ri); | |
| 334 | assert(i < @as(u32, len) * 2); | |
| 335 | return @intCast(if (i < len) i else i - len); | |
| 336 | } | |
| 337 | ||
| 338 | pub fn prev(ri: RingIndex, len: u31) RingIndex { | |
| 339 | const i = @intFromEnum(ri); | |
| 340 | const double_len = @as(u32, len) * 2; | |
| 341 | assert(i <= double_len); | |
| 342 | return @enumFromInt((if (i > 0) i else double_len) - 1); | |
| 343 | } | |
| 344 | ||
| 345 | pub fn next(ri: RingIndex, len: u31) RingIndex { | |
| 346 | const i = @intFromEnum(ri) + 1; | |
| 347 | const double_len = @as(u32, len) * 2; | |
| 348 | assert(i <= double_len); | |
| 349 | return @enumFromInt(if (i < double_len) i else 0); | |
| 350 | } | |
| 351 | }; | |
| 352 | ||
| 353 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | |
| 317 | 354 | |
| 318 | pub fn init(operations: []Operation) Batch { | |
| 319 | return .{ .operations = operations, .index = 0, .reserved = null }; | |
| 355 | pub fn init(operations: []Operation, ring: []u32) Batch { | |
| 356 | const len: u31 = @intCast(operations.len); | |
| 357 | assert(ring.len == len); | |
| 358 | return .{ | |
| 359 | .operations = operations, | |
| 360 | .ring = ring.ptr, | |
| 361 | .user = .{ | |
| 362 | .submit_tail = @enumFromInt(0), | |
| 363 | .complete_head = @enumFromInt(0), | |
| 364 | .complete_tail = @enumFromInt(0), | |
| 365 | }, | |
| 366 | .impl = .{ | |
| 367 | .submit_head = @enumFromInt(0), | |
| 368 | .submit_tail = @enumFromInt(0), | |
| 369 | .complete_tail = @enumFromInt(0), | |
| 370 | .reserved = null, | |
| 371 | }, | |
| 372 | }; | |
| 320 | 373 | } |
| 321 | 374 | |
| 322 | /// Submits all non-noop `operations`. | |
| 323 | pub fn submit(b: *Batch, io: Io) void { | |
| 324 | return io.vtable.batchSubmit(io.userdata, b); | |
| 375 | /// Adds `b.operations[operation]` to the list of submitted operations | |
| 376 | /// that will be performed when `wait` is called. | |
| 377 | pub fn add(b: *Batch, operation: usize) void { | |
| 378 | const tail = b.user.submit_tail; | |
| 379 | const len: u31 = @intCast(b.operations.len); | |
| 380 | b.user.submit_tail = tail.next(len); | |
| 381 | b.ring[0..len][tail.index(len)] = @intCast(operation); | |
| 325 | 382 | } |
| 326 | 383 | |
| 327 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | |
| 384 | fn flush(b: *Batch) void { | |
| 385 | @atomicStore(RingIndex, &b.impl.submit_tail, b.user.submit_tail, .release); | |
| 386 | } | |
| 328 | 387 | |
| 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. | |
| 388 | /// Returns `operation` such that `b.operations[operation]` has completed. | |
| 389 | /// Returns `null` when `wait` should be called. | |
| 390 | pub fn next(b: *Batch) ?u32 { | |
| 391 | const head = b.user.complete_head; | |
| 392 | if (head == b.user.complete_tail) { | |
| 393 | @branchHint(.unlikely); | |
| 394 | b.flush(); | |
| 395 | const tail = @atomicLoad(RingIndex, &b.impl.complete_tail, .acquire); | |
| 396 | if (head == tail) { | |
| 397 | @branchHint(.unlikely); | |
| 398 | return null; | |
| 399 | } | |
| 400 | assert(head != tail); | |
| 401 | b.user.complete_tail = tail; | |
| 402 | } | |
| 403 | const len: u31 = @intCast(b.operations.len); | |
| 404 | b.user.complete_head = head.next(len); | |
| 405 | return b.ring[0..len][head.index(len)]; | |
| 406 | } | |
| 407 | ||
| 408 | /// Starts work on any submitted operations and returns when at least one has completeed. | |
| 336 | 409 | /// |
| 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); | |
| 410 | /// Returns `error.Timeout` if `timeout` expires first. | |
| 411 | pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { | |
| 412 | return io.vtable.batchWait(io.userdata, b, timeout); | |
| 340 | 413 | } |
| 341 | 414 | |
| 342 | /// Returns after all `operations` have completed. Each operation | |
| 343 | /// independently may or may not have been canceled. | |
| 415 | /// Returns after all `operations` have completed. Operations which have not completed | |
| 416 | /// after this function returns were successfully dropped and had no side effects. | |
| 344 | 417 | pub fn cancel(b: *Batch, io: Io) void { |
| 345 | 418 | return io.vtable.batchCancel(io.userdata, b); |
| 346 | 419 | } |
lib/std/Io/File.zig+1-1| ... | ... | @@ -558,7 +558,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 558 | 558 | .file = file, |
| 559 | 559 | .data = buffer, |
| 560 | 560 | } }; |
| 561 | io.operate(&operation); | |
| 561 | try io.operate(&operation); | |
| 562 | 562 | return operation.file_read_streaming.status.result; |
| 563 | 563 | } |
| 564 | 564 |
lib/std/Io/File/Reader.zig+1-21| ... | ... | @@ -26,27 +26,7 @@ size_err: ?SizeError = null, |
| 26 | 26 | seek_err: ?SeekError = null, |
| 27 | 27 | interface: Io.Reader, |
| 28 | 28 | |
| 29 | pub const Error = error{ | |
| 30 | InputOutput, | |
| 31 | SystemResources, | |
| 32 | /// Trying to read a directory file descriptor as if it were a file. | |
| 33 | IsDir, | |
| 34 | BrokenPipe, | |
| 35 | ConnectionResetByPeer, | |
| 36 | /// File was not opened with read capability. | |
| 37 | NotOpenForReading, | |
| 38 | SocketUnconnected, | |
| 39 | /// Non-blocking has been enabled, and reading from the file descriptor | |
| 40 | /// would block. | |
| 41 | WouldBlock, | |
| 42 | /// In WASI, this error occurs when the file descriptor does | |
| 43 | /// not hold the required rights to read from it. | |
| 44 | AccessDenied, | |
| 45 | /// Unable to read file due to lock. Depending on the `Io` implementation, | |
| 46 | /// reading from a locked file may return this error, or may ignore the | |
| 47 | /// lock. | |
| 48 | LockViolation, | |
| 49 | } || Io.Cancelable || Io.UnexpectedError; | |
| 29 | pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; | |
| 50 | 30 | |
| 51 | 31 | pub const SizeError = File.StatError || error{ |
| 52 | 32 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. |
lib/std/Io/Threaded.zig+102-49| ... | ... | @@ -1587,8 +1587,7 @@ pub fn io(t: *Threaded) Io { |
| 1587 | 1587 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1588 | 1588 | .futexWake = futexWake, |
| 1589 | 1589 | |
| 1590 | .batch = batch, | |
| 1591 | .batchSubmit = batchSubmit, | |
| 1590 | .operate = operate, | |
| 1592 | 1591 | .batchWait = batchWait, |
| 1593 | 1592 | .batchCancel = batchCancel, |
| 1594 | 1593 | |
| ... | ... | @@ -1751,8 +1750,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1751 | 1750 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1752 | 1751 | .futexWake = futexWake, |
| 1753 | 1752 | |
| 1754 | .batch = batch, | |
| 1755 | .batchSubmit = batchSubmit, | |
| 1753 | .operate = operate, | |
| 1756 | 1754 | .batchWait = batchWait, |
| 1757 | 1755 | .batchCancel = batchCancel, |
| 1758 | 1756 | |
| ... | ... | @@ -2456,59 +2454,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2456 | 2454 | Thread.futexWake(ptr, max_waiters); |
| 2457 | 2455 | } |
| 2458 | 2456 | |
| 2459 | fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { | |
| 2457 | fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { | |
| 2460 | 2458 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2461 | 2459 | _ = t; |
| 2462 | _ = b; | |
| 2463 | return; | |
| 2464 | } | |
| 2465 | ||
| 2466 | fn operate(op: *Io.Operation) void { | |
| 2467 | 2460 | switch (op.*) { |
| 2468 | .noop => {}, | |
| 2469 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | |
| 2461 | .noop => |*o| { | |
| 2462 | _ = o.status.unstarted; | |
| 2463 | o.status = .{ .result = {} }; | |
| 2464 | }, | |
| 2465 | .file_read_streaming => |*o| { | |
| 2466 | _ = o.status.unstarted; | |
| 2467 | o.status = .{ .result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { | |
| 2468 | error.Canceled => return error.Canceled, | |
| 2469 | else => |e| e, | |
| 2470 | } }; | |
| 2471 | }, | |
| 2470 | 2472 | } |
| 2471 | 2473 | } |
| 2472 | 2474 | |
| 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; | |
| 2475 | fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { | |
| 2480 | 2476 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2481 | 2477 | const operations = b.operations; |
| 2482 | if (operations.len == 1) { | |
| 2483 | operate(&operations[0]); | |
| 2484 | return b.operations.len; | |
| 2478 | const len: u31 = @intCast(operations.len); | |
| 2479 | const ring = b.ring[0..len]; | |
| 2480 | var submit_head = b.impl.submit_head; | |
| 2481 | const submit_tail = b.user.submit_tail; | |
| 2482 | b.impl.submit_tail = submit_tail; | |
| 2483 | var complete_tail = b.impl.complete_tail; | |
| 2484 | var map_buffer: [poll_buffer_len]u32 = undefined; // poll_buffer index to operations index | |
| 2485 | var poll_i: usize = 0; | |
| 2486 | defer { | |
| 2487 | for (map_buffer[0..poll_i]) |op| { | |
| 2488 | submit_head = submit_head.prev(len); | |
| 2489 | ring[submit_head.index(len)] = op; | |
| 2490 | } | |
| 2491 | b.impl.submit_head = submit_head; | |
| 2492 | b.impl.complete_tail = complete_tail; | |
| 2493 | b.user.complete_tail = complete_tail; | |
| 2485 | 2494 | } |
| 2486 | 2495 | if (is_windows) @panic("TODO"); |
| 2487 | ||
| 2488 | 2496 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2489 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | |
| 2490 | var poll_i: usize = 0; | |
| 2491 | ||
| 2492 | for (operations, 0..) |*op, operation_index| switch (op.*) { | |
| 2493 | .noop => continue, | |
| 2494 | .file_read_streaming => |*o| { | |
| 2495 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | |
| 2496 | poll_buffer[poll_i] = .{ | |
| 2497 | .fd = o.file.handle, | |
| 2498 | .events = posix.POLL.IN, | |
| 2499 | .revents = 0, | |
| 2500 | }; | |
| 2501 | map_buffer[poll_i] = @intCast(operation_index); | |
| 2502 | poll_i += 1; | |
| 2497 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | |
| 2498 | const op = ring[submit_head.index(len)]; | |
| 2499 | const operation = &operations[op]; | |
| 2500 | switch (operation.*) { | |
| 2501 | else => { | |
| 2502 | try operate(t, operation); | |
| 2503 | ring[complete_tail.index(len)] = op; | |
| 2504 | complete_tail = complete_tail.next(len); | |
| 2505 | }, | |
| 2506 | .file_read_streaming => |*o| { | |
| 2507 | _ = o.status.unstarted; | |
| 2508 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | |
| 2509 | poll_buffer[poll_i] = .{ | |
| 2510 | .fd = o.file.handle, | |
| 2511 | .events = posix.POLL.IN, | |
| 2512 | .revents = 0, | |
| 2513 | }; | |
| 2514 | map_buffer[poll_i] = op; | |
| 2515 | poll_i += 1; | |
| 2516 | }, | |
| 2517 | } | |
| 2518 | } | |
| 2519 | switch (poll_i) { | |
| 2520 | 0 => return, | |
| 2521 | 1 => if (timeout == .none) { | |
| 2522 | const op = map_buffer[0]; | |
| 2523 | try operate(t, &operations[op]); | |
| 2524 | ring[complete_tail.index(len)] = op; | |
| 2525 | complete_tail = complete_tail.next(len); | |
| 2526 | return; | |
| 2503 | 2527 | }, |
| 2504 | }; | |
| 2505 | ||
| 2506 | if (poll_i == 0) return operations.len; | |
| 2507 | ||
| 2528 | else => {}, | |
| 2529 | } | |
| 2508 | 2530 | const t_io = ioBasic(t); |
| 2509 | 2531 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2510 | 2532 | const max_poll_ms = std.math.maxInt(i32); |
| 2511 | ||
| 2512 | 2533 | while (true) { |
| 2513 | 2534 | const timeout_ms: i32 = if (deadline) |d| t: { |
| 2514 | 2535 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| ... | ... | @@ -2526,11 +2547,24 @@ fn batchWait( |
| 2526 | 2547 | if (deadline == null) continue; |
| 2527 | 2548 | return error.Timeout; |
| 2528 | 2549 | } |
| 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; | |
| 2550 | var canceled = false; | |
| 2551 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { | |
| 2552 | if (poll_fd.revents == 0) { | |
| 2553 | submit_head = submit_head.prev(len); | |
| 2554 | ring[submit_head.index(len)] = op; | |
| 2555 | } else { | |
| 2556 | operate(t, &operations[op]) catch |err| switch (err) { | |
| 2557 | error.Canceled => { | |
| 2558 | canceled = true; | |
| 2559 | continue; | |
| 2560 | }, | |
| 2561 | }; | |
| 2562 | ring[complete_tail.index(len)] = op; | |
| 2563 | complete_tail = complete_tail.next(len); | |
| 2564 | } | |
| 2533 | 2565 | } |
| 2566 | poll_i = 0; | |
| 2567 | return if (canceled) error.Canceled; | |
| 2534 | 2568 | }, |
| 2535 | 2569 | .INTR => continue, |
| 2536 | 2570 | else => return error.ConcurrencyUnavailable, |
| ... | ... | @@ -2540,9 +2574,27 @@ fn batchWait( |
| 2540 | 2574 | |
| 2541 | 2575 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2542 | 2576 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2543 | _ = t; | |
| 2544 | _ = b; | |
| 2545 | return; | |
| 2577 | const operations = b.operations; | |
| 2578 | const len: u31 = @intCast(operations.len); | |
| 2579 | const ring = b.ring[0..len]; | |
| 2580 | var submit_head = b.impl.submit_head; | |
| 2581 | const submit_tail = b.user.submit_tail; | |
| 2582 | b.impl.submit_tail = submit_tail; | |
| 2583 | var complete_tail = b.impl.complete_tail; | |
| 2584 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | |
| 2585 | const op = ring[submit_head.index(len)]; | |
| 2586 | switch (operations[op]) { | |
| 2587 | .noop => { | |
| 2588 | operate(t, &operations[op]) catch unreachable; | |
| 2589 | ring[complete_tail.index(len)] = op; | |
| 2590 | complete_tail = complete_tail.next(len); | |
| 2591 | }, | |
| 2592 | .file_read_streaming => |*o| _ = o.status.unstarted, | |
| 2593 | } | |
| 2594 | } | |
| 2595 | b.impl.submit_head = submit_tail; | |
| 2596 | b.impl.complete_tail = complete_tail; | |
| 2597 | b.user.complete_tail = complete_tail; | |
| 2546 | 2598 | } |
| 2547 | 2599 | |
| 2548 | 2600 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { |
| ... | ... | @@ -10352,6 +10404,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 10352 | 10404 | |
| 10353 | 10405 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 10354 | 10406 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10407 | if (timeout == .none) return; | |
| 10355 | 10408 | if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); |
| 10356 | 10409 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 10357 | 10410 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); |
lib/std/process/Child.zig+33-39| ... | ... | @@ -149,51 +149,45 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) |
| 149 | 149 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; |
| 150 | 150 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; |
| 151 | 151 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; |
| 152 | var dones: [2]bool = .{ false, false }; | |
| 153 | 152 | var reads: [2]Io.Operation = undefined; |
| 154 | 153 | var vecs: [2][1][]u8 = undefined; |
| 155 | while (true) { | |
| 156 | for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { | |
| 157 | if (done) { | |
| 158 | read.* = .{ .noop = .{} }; | |
| 159 | continue; | |
| 160 | } | |
| 161 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); | |
| 162 | const cap = list.unusedCapacitySlice(); | |
| 163 | if (cap.len == 0) return error.StreamTooLong; | |
| 164 | vec[0] = cap; | |
| 165 | read.* = .{ .file_read_streaming = .{ | |
| 166 | .file = file, | |
| 167 | .data = vec, | |
| 168 | } }; | |
| 154 | var ring: [2]u32 = undefined; | |
| 155 | var batch: Io.Batch = .init(&reads, &ring); | |
| 156 | defer { | |
| 157 | batch.cancel(io); | |
| 158 | while (batch.next()) |op| { | |
| 159 | lists[op].items.len += reads[op].file_read_streaming.status.result catch continue; | |
| 169 | 160 | } |
| 170 | var all_done = true; | |
| 171 | var any_canceled = false; | |
| 172 | var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; | |
| 173 | try io.vtable.batch(io.userdata, &reads); | |
| 174 | for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { | |
| 175 | if (done.*) continue; | |
| 176 | const n = read.file_read_streaming.status.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 | }; | |
| 161 | } | |
| 162 | var remaining: usize = 0; | |
| 163 | for (0.., &reads, &lists, &files, &vecs) |op, *read, list, file, *vec| { | |
| 164 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); | |
| 165 | const cap = list.unusedCapacitySlice(); | |
| 166 | if (cap.len == 0) return error.StreamTooLong; | |
| 167 | vec[0] = cap; | |
| 168 | read.* = .{ .file_read_streaming = .{ | |
| 169 | .file = file, | |
| 170 | .data = vec, | |
| 171 | } }; | |
| 172 | batch.add(op); | |
| 173 | remaining += 1; | |
| 174 | } | |
| 175 | while (remaining > 0) { | |
| 176 | try batch.wait(io, .none); | |
| 177 | while (batch.next()) |op| { | |
| 178 | const n = try reads[op].file_read_streaming.status.result; | |
| 187 | 179 | if (n == 0) { |
| 188 | done.* = true; | |
| 180 | remaining -= 1; | |
| 189 | 181 | } else { |
| 190 | all_done = false; | |
| 182 | lists[op].items.len += n; | |
| 183 | if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; | |
| 184 | if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); | |
| 185 | const cap = lists[op].unusedCapacitySlice(); | |
| 186 | if (cap.len == 0) return error.StreamTooLong; | |
| 187 | vecs[op][0] = cap; | |
| 188 | reads[op].file_read_streaming.status = .{ .unstarted = {} }; | |
| 189 | batch.add(op); | |
| 191 | 190 | } |
| 192 | list.items.len += n; | |
| 193 | if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; | |
| 194 | 191 | } |
| 195 | if (any_canceled) return error.Canceled; | |
| 196 | try other_err; | |
| 197 | if (all_done) return; | |
| 198 | 192 | } |
| 199 | 193 | } |