| 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,9 +149,8 @@ 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 | batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, | 152 | operate: *const fn (?*anyopaque, *Operation) Cancelable!void, |
| 153 | batchSubmit: *const fn (?*anyopaque, *Batch) void, | 153 | batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, |
| 154 | batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, | ||
| 155 | batchCancel: *const fn (?*anyopaque, *Batch) void, | 154 | batchCancel: *const fn (?*anyopaque, *Batch) void, |
| 156 | 155 | ||
| 157 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, | 156 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| ... | @@ -261,48 +260,50 @@ pub const Operation = union(enum) { | ... | @@ -261,48 +260,50 @@ pub const Operation = union(enum) { |
| 261 | 260 | ||
| 262 | pub const Noop = struct { | 261 | pub const Noop = struct { |
| 263 | reserved: [2]usize = .{ 0, 0 }, | 262 | reserved: [2]usize = .{ 0, 0 }, |
| 264 | status: Status(void) = .{ .result = {} }, | 263 | status: Status(void) = .{ .unstarted = {} }, |
| 265 | }; | 264 | }; |
| 266 | 265 | ||
| 267 | /// Returns 0 on end of stream. | 266 | /// Returns 0 on end of stream. |
| 268 | pub const FileReadStreaming = struct { | 267 | pub const FileReadStreaming = struct { |
| 269 | file: File, | 268 | file: File, |
| 270 | data: []const []u8, | 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 | pub fn Status(Result: type) type { | 295 | pub fn Status(Result: type) type { |
| 275 | return union { | 296 | return union { |
| 276 | unstarted: void, | 297 | unstarted: void, |
| 277 | pending: usize, | 298 | pending: *Batch, |
| 278 | result: Result, | 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 | /// Performs one `Operation`. | 304 | /// Performs one `Operation`. |
| 304 | pub fn operate(io: Io, operation: *Operation) void { | 305 | pub fn operate(io: Io, operation: *Operation) Cancelable!void { |
| 305 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | 306 | return io.vtable.operate(io.userdata, operation) catch unreachable; |
| 306 | } | 307 | } |
| 307 | 308 | ||
| 308 | /// Submits many operations together without waiting for all of them to | 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,35 +313,107 @@ pub fn operate(io: Io, operation: *Operation) void { |
| 312 | /// level API that operates on `Future`, see `Select`. | 313 | /// level API that operates on `Future`, see `Select`. |
| 313 | pub const Batch = struct { | 314 | pub const Batch = struct { |
| 314 | operations: []Operation, | 315 | operations: []Operation, |
| 315 | index: usize, | 316 | ring: [*]u32, |
| 316 | reserved: ?*anyopaque, | 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 { | 355 | pub fn init(operations: []Operation, ring: []u32) Batch { |
| 319 | return .{ .operations = operations, .index = 0, .reserved = null }; | 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`. | 375 | /// Adds `b.operations[operation]` to the list of submitted operations |
| 323 | pub fn submit(b: *Batch, io: Io) void { | 376 | /// that will be performed when `wait` is called. |
| 324 | return io.vtable.batchSubmit(io.userdata, b); | 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 | 388 | /// Returns `operation` such that `b.operations[operation]` has completed. |
| 330 | /// indexes given by `resubmissions`. This set of indexes typically will be empty | 389 | /// Returns `null` when `wait` should be called. |
| 331 | /// on the first call to `await` since all operations have already been | 390 | pub fn next(b: *Batch) ?u32 { |
| 332 | /// submitted via `async`. | 391 | const head = b.user.complete_head; |
| 333 | /// | 392 | if (head == b.user.complete_tail) { |
| 334 | /// Returns the index of a completed `Operation`, or `operations.len` if | 393 | @branchHint(.unlikely); |
| 335 | /// all operations are completed. | 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. | 410 | /// Returns `error.Timeout` if `timeout` expires first. |
| 338 | pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { | 411 | pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { |
| 339 | return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); | 412 | return io.vtable.batchWait(io.userdata, b, timeout); |
| 340 | } | 413 | } |
| 341 | 414 | ||
| 342 | /// Returns after all `operations` have completed. Each operation | 415 | /// Returns after all `operations` have completed. Operations which have not completed |
| 343 | /// independently may or may not have been canceled. | 416 | /// after this function returns were successfully dropped and had no side effects. |
| 344 | pub fn cancel(b: *Batch, io: Io) void { | 417 | pub fn cancel(b: *Batch, io: Io) void { |
| 345 | return io.vtable.batchCancel(io.userdata, b); | 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,7 +558,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 558 | .file = file, | 558 | .file = file, |
| 559 | .data = buffer, | 559 | .data = buffer, |
| 560 | } }; | 560 | } }; |
| 561 | io.operate(&operation); | 561 | try io.operate(&operation); |
| 562 | return operation.file_read_streaming.status.result; | 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,27 +26,7 @@ size_err: ?SizeError = null, |
| 26 | seek_err: ?SeekError = null, | 26 | seek_err: ?SeekError = null, |
| 27 | interface: Io.Reader, | 27 | interface: Io.Reader, |
| 28 | 28 | ||
| 29 | pub const Error = error{ | 29 | pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; |
| 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; | ||
| 50 | 30 | ||
| 51 | pub const SizeError = File.StatError || error{ | 31 | pub const SizeError = File.StatError || error{ |
| 52 | /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. | 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,8 +1587,7 @@ pub fn io(t: *Threaded) Io { |
| 1587 | .futexWaitUncancelable = futexWaitUncancelable, | 1587 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1588 | .futexWake = futexWake, | 1588 | .futexWake = futexWake, |
| 1589 | 1589 | ||
| 1590 | .batch = batch, | 1590 | .operate = operate, |
| 1591 | .batchSubmit = batchSubmit, | ||
| 1592 | .batchWait = batchWait, | 1591 | .batchWait = batchWait, |
| 1593 | .batchCancel = batchCancel, | 1592 | .batchCancel = batchCancel, |
| 1594 | 1593 | ||
| ... | @@ -1751,8 +1750,7 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1751,8 +1750,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1751 | .futexWaitUncancelable = futexWaitUncancelable, | 1750 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1752 | .futexWake = futexWake, | 1751 | .futexWake = futexWake, |
| 1753 | 1752 | ||
| 1754 | .batch = batch, | 1753 | .operate = operate, |
| 1755 | .batchSubmit = batchSubmit, | ||
| 1756 | .batchWait = batchWait, | 1754 | .batchWait = batchWait, |
| 1757 | .batchCancel = batchCancel, | 1755 | .batchCancel = batchCancel, |
| 1758 | 1756 | ||
| ... | @@ -2456,59 +2454,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -2456,59 +2454,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2456 | Thread.futexWake(ptr, max_waiters); | 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 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2458 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2461 | _ = t; | 2459 | _ = t; |
| 2462 | _ = b; | ||
| 2463 | return; | ||
| 2464 | } | ||
| 2465 | |||
| 2466 | fn operate(op: *Io.Operation) void { | ||
| 2467 | switch (op.*) { | 2460 | switch (op.*) { |
| 2468 | .noop => {}, | 2461 | .noop => |*o| { |
| 2469 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | 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( | 2475 | fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { |
| 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)); | 2476 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2481 | const operations = b.operations; | 2477 | const operations = b.operations; |
| 2482 | if (operations.len == 1) { | 2478 | const len: u31 = @intCast(operations.len); |
| 2483 | operate(&operations[0]); | 2479 | const ring = b.ring[0..len]; |
| 2484 | return b.operations.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 | if (is_windows) @panic("TODO"); | 2495 | if (is_windows) @panic("TODO"); |
| 2487 | |||
| 2488 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | 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 | 2497 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { |
| 2490 | var poll_i: usize = 0; | 2498 | const op = ring[submit_head.index(len)]; |
| 2491 | 2499 | const operation = &operations[op]; | |
| 2492 | for (operations, 0..) |*op, operation_index| switch (op.*) { | 2500 | switch (operation.*) { |
| 2493 | .noop => continue, | 2501 | else => { |
| 2494 | .file_read_streaming => |*o| { | 2502 | try operate(t, operation); |
| 2495 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | 2503 | ring[complete_tail.index(len)] = op; |
| 2496 | poll_buffer[poll_i] = .{ | 2504 | complete_tail = complete_tail.next(len); |
| 2497 | .fd = o.file.handle, | 2505 | }, |
| 2498 | .events = posix.POLL.IN, | 2506 | .file_read_streaming => |*o| { |
| 2499 | .revents = 0, | 2507 | _ = o.status.unstarted; |
| 2500 | }; | 2508 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; |
| 2501 | map_buffer[poll_i] = @intCast(operation_index); | 2509 | poll_buffer[poll_i] = .{ |
| 2502 | poll_i += 1; | 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 | }; | 2528 | else => {}, |
| 2505 | 2529 | } | |
| 2506 | if (poll_i == 0) return operations.len; | ||
| 2507 | |||
| 2508 | const t_io = ioBasic(t); | 2530 | const t_io = ioBasic(t); |
| 2509 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; | 2531 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2510 | const max_poll_ms = std.math.maxInt(i32); | 2532 | const max_poll_ms = std.math.maxInt(i32); |
| 2511 | |||
| 2512 | while (true) { | 2533 | while (true) { |
| 2513 | const timeout_ms: i32 = if (deadline) |d| t: { | 2534 | const timeout_ms: i32 = if (deadline) |d| t: { |
| 2514 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; | 2535 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| ... | @@ -2526,11 +2547,24 @@ fn batchWait( | ... | @@ -2526,11 +2547,24 @@ fn batchWait( |
| 2526 | if (deadline == null) continue; | 2547 | if (deadline == null) continue; |
| 2527 | return error.Timeout; | 2548 | return error.Timeout; |
| 2528 | } | 2549 | } |
| 2529 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | 2550 | var canceled = false; |
| 2530 | if (poll_fd.revents == 0) continue; | 2551 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { |
| 2531 | operate(&operations[i]); | 2552 | if (poll_fd.revents == 0) { |
| 2532 | return i; | 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 | .INTR => continue, | 2569 | .INTR => continue, |
| 2536 | else => return error.ConcurrencyUnavailable, | 2570 | else => return error.ConcurrencyUnavailable, |
| ... | @@ -2540,9 +2574,27 @@ fn batchWait( | ... | @@ -2540,9 +2574,27 @@ fn batchWait( |
| 2540 | 2574 | ||
| 2541 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { | 2575 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2542 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2576 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2543 | _ = t; | 2577 | const operations = b.operations; |
| 2544 | _ = b; | 2578 | const len: u31 = @intCast(operations.len); |
| 2545 | return; | 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 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { | 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,6 +10404,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 10352 | 10404 | ||
| 10353 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | 10405 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 10354 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 10406 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10407 | if (timeout == .none) return; | ||
| 10355 | if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); | 10408 | if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); |
| 10356 | if (native_os == .wasi) return sleepWasi(t, timeout); | 10409 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 10357 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); | 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,51 +149,45 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) |
| 149 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; | 149 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; |
| 150 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; | 150 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; |
| 151 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; | 151 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; |
| 152 | var dones: [2]bool = .{ false, false }; | ||
| 153 | var reads: [2]Io.Operation = undefined; | 152 | var reads: [2]Io.Operation = undefined; |
| 154 | var vecs: [2][1][]u8 = undefined; | 153 | var vecs: [2][1][]u8 = undefined; |
| 155 | while (true) { | 154 | var ring: [2]u32 = undefined; |
| 156 | for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { | 155 | var batch: Io.Batch = .init(&reads, &ring); |
| 157 | if (done) { | 156 | defer { |
| 158 | read.* = .{ .noop = .{} }; | 157 | batch.cancel(io); |
| 159 | continue; | 158 | while (batch.next()) |op| { |
| 160 | } | 159 | lists[op].items.len += reads[op].file_read_streaming.status.result catch continue; |
| 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 | } }; | ||
| 169 | } | 160 | } |
| 170 | var all_done = true; | 161 | } |
| 171 | var any_canceled = false; | 162 | var remaining: usize = 0; |
| 172 | var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; | 163 | for (0.., &reads, &lists, &files, &vecs) |op, *read, list, file, *vec| { |
| 173 | try io.vtable.batch(io.userdata, &reads); | 164 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); |
| 174 | for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { | 165 | const cap = list.unusedCapacitySlice(); |
| 175 | if (done.*) continue; | 166 | if (cap.len == 0) return error.StreamTooLong; |
| 176 | const n = read.file_read_streaming.status.result catch |err| switch (err) { | 167 | vec[0] = cap; |
| 177 | error.Canceled => { | 168 | read.* = .{ .file_read_streaming = .{ |
| 178 | any_canceled = true; | 169 | .file = file, |
| 179 | continue; | 170 | .data = vec, |
| 180 | }, | 171 | } }; |
| 181 | error.WouldBlock => continue, | 172 | batch.add(op); |
| 182 | else => |e| { | 173 | remaining += 1; |
| 183 | other_err = e; | 174 | } |
| 184 | continue; | 175 | while (remaining > 0) { |
| 185 | }, | 176 | try batch.wait(io, .none); |
| 186 | }; | 177 | while (batch.next()) |op| { |
| 178 | const n = try reads[op].file_read_streaming.status.result; | ||
| 187 | if (n == 0) { | 179 | if (n == 0) { |
| 188 | done.* = true; | 180 | remaining -= 1; |
| 189 | } else { | 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 | } |