| author | |
| committer | |
| log | f633a4845aeea5f390d4ea1b05e5d957efb02966 |
| tree | bcc9fb652281816f12f6aae1bc7b72d3cc7addbb |
| parent | 078a19cf3111726f4a3861da9d4e297eac7dc585 |
5 files changed, 259 insertions(+), 159 deletions(-)
lib/std/Io.zig+122-48| ... | ... | @@ -148,9 +148,8 @@ pub const VTable = struct { |
| 148 | 148 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 149 | 149 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 150 | 150 | |
| 151 | batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, | |
| 152 | batchSubmit: *const fn (?*anyopaque, *Batch) void, | |
| 153 | batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, | |
| 151 | operate: *const fn (?*anyopaque, *Operation) Cancelable!void, | |
| 152 | batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, | |
| 154 | 153 | batchCancel: *const fn (?*anyopaque, *Batch) void, |
| 155 | 154 | |
| 156 | 155 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| ... | ... | @@ -252,48 +251,51 @@ pub const Operation = union(enum) { |
| 252 | 251 | |
| 253 | 252 | pub const Noop = struct { |
| 254 | 253 | reserved: [2]usize = .{ 0, 0 }, |
| 255 | status: Status(void) = .{ .result = {} }, | |
| 254 | status: Status(void) = .{ .unstarted = {} }, | |
| 256 | 255 | }; |
| 257 | 256 | |
| 258 | 257 | /// Returns 0 on end of stream. |
| 259 | 258 | pub const FileReadStreaming = struct { |
| 260 | 259 | file: File, |
| 261 | 260 | data: []const []u8, |
| 262 | status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, | |
| 261 | status: Status(Error!usize) = .{ .unstarted = {} }, | |
| 262 | ||
| 263 | pub const Error = error{ | |
| 264 | InputOutput, | |
| 265 | SystemResources, | |
| 266 | IsDir, | |
| 267 | BrokenPipe, | |
| 268 | ConnectionResetByPeer, | |
| 269 | Timeout, | |
| 270 | /// In WASI, EBADF is mapped to this error because it is returned when | |
| 271 | /// trying to read a directory file descriptor as if it were a file. | |
| 272 | NotOpenForReading, | |
| 273 | SocketUnconnected, | |
| 274 | /// Non-blocking has been enabled, and reading from the file descriptor | |
| 275 | /// would block. | |
| 276 | WouldBlock, | |
| 277 | /// In WASI, this error occurs when the file descriptor does | |
| 278 | /// not hold the required rights to read from it. | |
| 279 | AccessDenied, | |
| 280 | /// Unable to read file due to lock. Depending on the `Io` implementation, | |
| 281 | /// reading from a locked file may return this error, or may ignore the | |
| 282 | /// lock. | |
| 283 | LockViolation, | |
| 284 | } || Io.UnexpectedError; | |
| 263 | 285 | }; |
| 264 | 286 | |
| 265 | 287 | pub fn Status(Result: type) type { |
| 266 | 288 | return union { |
| 267 | 289 | unstarted: void, |
| 268 | pending: usize, | |
| 290 | pending: *Batch, | |
| 269 | 291 | result: Result, |
| 270 | 292 | }; |
| 271 | 293 | } |
| 272 | 294 | }; |
| 273 | 295 | |
| 274 | /// Performs all `operations` in an unspecified order, concurrently. | |
| 275 | /// | |
| 276 | /// Returns after all `operations` have been completed. If the operations could | |
| 277 | /// not be completed concurrently, returns `error.ConcurrencyUnavailable`. | |
| 278 | /// | |
| 279 | /// With this API, it is rare for concurrency to not be available. Even a | |
| 280 | /// single-threaded `Io` implementation can, for example, take advantage of | |
| 281 | /// poll() to implement this. Note that poll() is fallible however. | |
| 282 | /// | |
| 283 | /// If `operations.len` is one, `error.ConcurrencyUnavailable` is unreachable. | |
| 284 | /// | |
| 285 | /// On entry, all operations must already have `.status = .unstarted` except | |
| 286 | /// noops must have `.status = .{ .result = {} }`, to safety check the state | |
| 287 | /// transitions. | |
| 288 | /// | |
| 289 | /// On return, all operations have `.status = .{ .result = ... }`. | |
| 290 | pub fn batch(io: Io, operations: []Operation) ConcurrentError!void { | |
| 291 | return io.vtable.batch(io.userdata, operations); | |
| 292 | } | |
| 293 | ||
| 294 | 296 | /// Performs one `Operation`. |
| 295 | pub fn operate(io: Io, operation: *Operation) void { | |
| 296 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | |
| 297 | pub fn operate(io: Io, operation: *Operation) Cancelable!void { | |
| 298 | return io.vtable.operate(io.userdata, operation) catch unreachable; | |
| 297 | 299 | } |
| 298 | 300 | |
| 299 | 301 | /// Submits many operations together without waiting for all of them to |
| ... | ... | @@ -303,35 +305,107 @@ pub fn operate(io: Io, operation: *Operation) void { |
| 303 | 305 | /// level API that operates on `Future`, see `Select`. |
| 304 | 306 | pub const Batch = struct { |
| 305 | 307 | operations: []Operation, |
| 306 | index: usize, | |
| 307 | reserved: ?*anyopaque, | |
| 308 | ring: [*]u32, | |
| 309 | user: struct { | |
| 310 | submit_tail: RingIndex, | |
| 311 | complete_head: RingIndex, | |
| 312 | complete_tail: RingIndex, | |
| 313 | }, | |
| 314 | impl: struct { | |
| 315 | submit_head: RingIndex, | |
| 316 | submit_tail: RingIndex, | |
| 317 | complete_tail: RingIndex, | |
| 318 | reserved: ?*anyopaque, | |
| 319 | }, | |
| 320 | ||
| 321 | pub const RingIndex = enum(u32) { | |
| 322 | _, | |
| 323 | ||
| 324 | pub fn index(ri: RingIndex, len: u31) u31 { | |
| 325 | const i = @intFromEnum(ri); | |
| 326 | assert(i < @as(u32, len) * 2); | |
| 327 | return @intCast(if (i < len) i else i - len); | |
| 328 | } | |
| 329 | ||
| 330 | pub fn prev(ri: RingIndex, len: u31) RingIndex { | |
| 331 | const i = @intFromEnum(ri); | |
| 332 | const double_len = @as(u32, len) * 2; | |
| 333 | assert(i <= double_len); | |
| 334 | return @enumFromInt((if (i > 0) i else double_len) - 1); | |
| 335 | } | |
| 336 | ||
| 337 | pub fn next(ri: RingIndex, len: u31) RingIndex { | |
| 338 | const i = @intFromEnum(ri) + 1; | |
| 339 | const double_len = @as(u32, len) * 2; | |
| 340 | assert(i <= double_len); | |
| 341 | return @enumFromInt(if (i < double_len) i else 0); | |
| 342 | } | |
| 343 | }; | |
| 344 | ||
| 345 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | |
| 308 | 346 | |
| 309 | pub fn init(operations: []Operation) Batch { | |
| 310 | return .{ .operations = operations, .index = 0, .reserved = null }; | |
| 347 | pub fn init(operations: []Operation, ring: []u32) Batch { | |
| 348 | const len: u31 = @intCast(operations.len); | |
| 349 | assert(ring.len == len); | |
| 350 | return .{ | |
| 351 | .operations = operations, | |
| 352 | .ring = ring.ptr, | |
| 353 | .user = .{ | |
| 354 | .submit_tail = @enumFromInt(0), | |
| 355 | .complete_head = @enumFromInt(0), | |
| 356 | .complete_tail = @enumFromInt(0), | |
| 357 | }, | |
| 358 | .impl = .{ | |
| 359 | .submit_head = @enumFromInt(0), | |
| 360 | .submit_tail = @enumFromInt(0), | |
| 361 | .complete_tail = @enumFromInt(0), | |
| 362 | .reserved = null, | |
| 363 | }, | |
| 364 | }; | |
| 311 | 365 | } |
| 312 | 366 | |
| 313 | /// Submits all non-noop `operations`. | |
| 314 | pub fn submit(b: *Batch, io: Io) void { | |
| 315 | return io.vtable.batchSubmit(io.userdata, b); | |
| 367 | /// Adds `b.operations[operation]` to the list of submitted operations | |
| 368 | /// that will be performed when `wait` is called. | |
| 369 | pub fn add(b: *Batch, operation: usize) void { | |
| 370 | const tail = b.user.submit_tail; | |
| 371 | const len: u31 = @intCast(b.operations.len); | |
| 372 | b.user.submit_tail = tail.next(len); | |
| 373 | b.ring[0..len][tail.index(len)] = @intCast(operation); | |
| 316 | 374 | } |
| 317 | 375 | |
| 318 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | |
| 376 | fn flush(b: *Batch) void { | |
| 377 | @atomicStore(RingIndex, &b.impl.submit_tail, b.user.submit_tail, .release); | |
| 378 | } | |
| 319 | 379 | |
| 320 | /// Resubmits the previously completed or noop-initialized `operations` at | |
| 321 | /// indexes given by `resubmissions`. This set of indexes typically will be empty | |
| 322 | /// on the first call to `await` since all operations have already been | |
| 323 | /// submitted via `async`. | |
| 324 | /// | |
| 325 | /// Returns the index of a completed `Operation`, or `operations.len` if | |
| 326 | /// all operations are completed. | |
| 380 | /// Returns `operation` such that `b.operations[operation]` has completed. | |
| 381 | /// Returns `null` when `wait` should be called. | |
| 382 | pub fn next(b: *Batch) ?u32 { | |
| 383 | const head = b.user.complete_head; | |
| 384 | if (head == b.user.complete_tail) { | |
| 385 | @branchHint(.unlikely); | |
| 386 | b.flush(); | |
| 387 | const tail = @atomicLoad(RingIndex, &b.impl.complete_tail, .acquire); | |
| 388 | if (head == tail) { | |
| 389 | @branchHint(.unlikely); | |
| 390 | return null; | |
| 391 | } | |
| 392 | assert(head != tail); | |
| 393 | b.user.complete_tail = tail; | |
| 394 | } | |
| 395 | const len: u31 = @intCast(b.operations.len); | |
| 396 | b.user.complete_head = head.next(len); | |
| 397 | return b.ring[0..len][head.index(len)]; | |
| 398 | } | |
| 399 | ||
| 400 | /// Starts work on any submitted operations and returns when at least one has completeed. | |
| 327 | 401 | /// |
| 328 | /// When `error.Canceled` is returned, all operations have already completed. | |
| 329 | pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { | |
| 330 | return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); | |
| 402 | /// Returns `error.Timeout` if `timeout` expires first. | |
| 403 | pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { | |
| 404 | return io.vtable.batchWait(io.userdata, b, timeout); | |
| 331 | 405 | } |
| 332 | 406 | |
| 333 | /// Returns after all `operations` have completed. Each operation | |
| 334 | /// independently may or may not have been canceled. | |
| 407 | /// Returns after all `operations` have completed. Operations which have not completed | |
| 408 | /// after this function returns were successfully dropped and had no side effects. | |
| 335 | 409 | pub fn cancel(b: *Batch, io: Io) void { |
| 336 | 410 | return io.vtable.batchCancel(io.userdata, b); |
| 337 | 411 | } |
lib/std/Io/File.zig+1-1| ... | ... | @@ -530,7 +530,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 530 | 530 | .file = file, |
| 531 | 531 | .data = buffer, |
| 532 | 532 | } }; |
| 533 | io.operate(&operation); | |
| 533 | try io.operate(&operation); | |
| 534 | 534 | return operation.file_read_streaming.status.result; |
| 535 | 535 | } |
| 536 | 536 |
lib/std/Io/File/Reader.zig+1-22| ... | ... | @@ -26,28 +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 | IsDir, | |
| 33 | BrokenPipe, | |
| 34 | ConnectionResetByPeer, | |
| 35 | Timeout, | |
| 36 | /// In WASI, EBADF is mapped to this error because it is returned when | |
| 37 | /// trying to read a directory file descriptor as if it were a file. | |
| 38 | NotOpenForReading, | |
| 39 | SocketUnconnected, | |
| 40 | /// Non-blocking has been enabled, and reading from the file descriptor | |
| 41 | /// would block. | |
| 42 | WouldBlock, | |
| 43 | /// In WASI, this error occurs when the file descriptor does | |
| 44 | /// not hold the required rights to read from it. | |
| 45 | AccessDenied, | |
| 46 | /// Unable to read file due to lock. Depending on the `Io` implementation, | |
| 47 | /// reading from a locked file may return this error, or may ignore the | |
| 48 | /// lock. | |
| 49 | LockViolation, | |
| 50 | } || Io.Cancelable || Io.UnexpectedError; | |
| 29 | pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; | |
| 51 | 30 | |
| 52 | 31 | pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ |
| 53 | 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| ... | ... | @@ -1438,8 +1438,7 @@ pub fn io(t: *Threaded) Io { |
| 1438 | 1438 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1439 | 1439 | .futexWake = futexWake, |
| 1440 | 1440 | |
| 1441 | .batch = batch, | |
| 1442 | .batchSubmit = batchSubmit, | |
| 1441 | .operate = operate, | |
| 1443 | 1442 | .batchWait = batchWait, |
| 1444 | 1443 | .batchCancel = batchCancel, |
| 1445 | 1444 | |
| ... | ... | @@ -1594,8 +1593,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1594 | 1593 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1595 | 1594 | .futexWake = futexWake, |
| 1596 | 1595 | |
| 1597 | .batch = batch, | |
| 1598 | .batchSubmit = batchSubmit, | |
| 1596 | .operate = operate, | |
| 1599 | 1597 | .batchWait = batchWait, |
| 1600 | 1598 | .batchCancel = batchCancel, |
| 1601 | 1599 | |
| ... | ... | @@ -2275,59 +2273,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2275 | 2273 | Thread.futexWake(ptr, max_waiters); |
| 2276 | 2274 | } |
| 2277 | 2275 | |
| 2278 | fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { | |
| 2276 | fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { | |
| 2279 | 2277 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2280 | 2278 | _ = t; |
| 2281 | _ = b; | |
| 2282 | return; | |
| 2283 | } | |
| 2284 | ||
| 2285 | fn operate(op: *Io.Operation) void { | |
| 2286 | 2279 | switch (op.*) { |
| 2287 | .noop => {}, | |
| 2288 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | |
| 2280 | .noop => |*o| { | |
| 2281 | _ = o.status.unstarted; | |
| 2282 | o.status = .{ .result = {} }; | |
| 2283 | }, | |
| 2284 | .file_read_streaming => |*o| { | |
| 2285 | _ = o.status.unstarted; | |
| 2286 | o.status = .{ .result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { | |
| 2287 | error.Canceled => return error.Canceled, | |
| 2288 | else => |e| e, | |
| 2289 | } }; | |
| 2290 | }, | |
| 2289 | 2291 | } |
| 2290 | 2292 | } |
| 2291 | 2293 | |
| 2292 | fn batchWait( | |
| 2293 | userdata: ?*anyopaque, | |
| 2294 | b: *Io.Batch, | |
| 2295 | resubmissions: []const usize, | |
| 2296 | timeout: Io.Timeout, | |
| 2297 | ) Io.Batch.WaitError!usize { | |
| 2298 | _ = resubmissions; | |
| 2294 | fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { | |
| 2299 | 2295 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2300 | 2296 | const operations = b.operations; |
| 2301 | if (operations.len == 1) { | |
| 2302 | operate(&operations[0]); | |
| 2303 | return b.operations.len; | |
| 2297 | const len: u31 = @intCast(operations.len); | |
| 2298 | const ring = b.ring[0..len]; | |
| 2299 | var submit_head = b.impl.submit_head; | |
| 2300 | const submit_tail = b.user.submit_tail; | |
| 2301 | b.impl.submit_tail = submit_tail; | |
| 2302 | var complete_tail = b.impl.complete_tail; | |
| 2303 | var map_buffer: [poll_buffer_len]u32 = undefined; // poll_buffer index to operations index | |
| 2304 | var poll_i: usize = 0; | |
| 2305 | defer { | |
| 2306 | for (map_buffer[0..poll_i]) |op| { | |
| 2307 | submit_head = submit_head.prev(len); | |
| 2308 | ring[submit_head.index(len)] = op; | |
| 2309 | } | |
| 2310 | b.impl.submit_head = submit_head; | |
| 2311 | b.impl.complete_tail = complete_tail; | |
| 2312 | b.user.complete_tail = complete_tail; | |
| 2304 | 2313 | } |
| 2305 | 2314 | if (is_windows) @panic("TODO"); |
| 2306 | ||
| 2307 | 2315 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2308 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | |
| 2309 | var poll_i: usize = 0; | |
| 2310 | ||
| 2311 | for (operations, 0..) |*op, operation_index| switch (op.*) { | |
| 2312 | .noop => continue, | |
| 2313 | .file_read_streaming => |*o| { | |
| 2314 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | |
| 2315 | poll_buffer[poll_i] = .{ | |
| 2316 | .fd = o.file.handle, | |
| 2317 | .events = posix.POLL.IN, | |
| 2318 | .revents = 0, | |
| 2319 | }; | |
| 2320 | map_buffer[poll_i] = @intCast(operation_index); | |
| 2321 | poll_i += 1; | |
| 2316 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | |
| 2317 | const op = ring[submit_head.index(len)]; | |
| 2318 | const operation = &operations[op]; | |
| 2319 | switch (operation.*) { | |
| 2320 | else => { | |
| 2321 | try operate(t, operation); | |
| 2322 | ring[complete_tail.index(len)] = op; | |
| 2323 | complete_tail = complete_tail.next(len); | |
| 2324 | }, | |
| 2325 | .file_read_streaming => |*o| { | |
| 2326 | _ = o.status.unstarted; | |
| 2327 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | |
| 2328 | poll_buffer[poll_i] = .{ | |
| 2329 | .fd = o.file.handle, | |
| 2330 | .events = posix.POLL.IN, | |
| 2331 | .revents = 0, | |
| 2332 | }; | |
| 2333 | map_buffer[poll_i] = op; | |
| 2334 | poll_i += 1; | |
| 2335 | }, | |
| 2336 | } | |
| 2337 | } | |
| 2338 | switch (poll_i) { | |
| 2339 | 0 => return, | |
| 2340 | 1 => if (timeout == .none) { | |
| 2341 | const op = map_buffer[0]; | |
| 2342 | try operate(t, &operations[op]); | |
| 2343 | ring[complete_tail.index(len)] = op; | |
| 2344 | complete_tail = complete_tail.next(len); | |
| 2345 | return; | |
| 2322 | 2346 | }, |
| 2323 | }; | |
| 2324 | ||
| 2325 | if (poll_i == 0) return operations.len; | |
| 2326 | ||
| 2347 | else => {}, | |
| 2348 | } | |
| 2327 | 2349 | const t_io = ioBasic(t); |
| 2328 | 2350 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2329 | 2351 | const max_poll_ms = std.math.maxInt(i32); |
| 2330 | ||
| 2331 | 2352 | while (true) { |
| 2332 | 2353 | const timeout_ms: i32 = if (deadline) |d| t: { |
| 2333 | 2354 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| ... | ... | @@ -2345,11 +2366,24 @@ fn batchWait( |
| 2345 | 2366 | if (deadline == null) continue; |
| 2346 | 2367 | return error.Timeout; |
| 2347 | 2368 | } |
| 2348 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | |
| 2349 | if (poll_fd.revents == 0) continue; | |
| 2350 | operate(&operations[i]); | |
| 2351 | return i; | |
| 2369 | var canceled = false; | |
| 2370 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { | |
| 2371 | if (poll_fd.revents == 0) { | |
| 2372 | submit_head = submit_head.prev(len); | |
| 2373 | ring[submit_head.index(len)] = op; | |
| 2374 | } else { | |
| 2375 | operate(t, &operations[op]) catch |err| switch (err) { | |
| 2376 | error.Canceled => { | |
| 2377 | canceled = true; | |
| 2378 | continue; | |
| 2379 | }, | |
| 2380 | }; | |
| 2381 | ring[complete_tail.index(len)] = op; | |
| 2382 | complete_tail = complete_tail.next(len); | |
| 2383 | } | |
| 2352 | 2384 | } |
| 2385 | poll_i = 0; | |
| 2386 | return if (canceled) error.Canceled; | |
| 2353 | 2387 | }, |
| 2354 | 2388 | .INTR => continue, |
| 2355 | 2389 | else => return error.ConcurrencyUnavailable, |
| ... | ... | @@ -2359,9 +2393,27 @@ fn batchWait( |
| 2359 | 2393 | |
| 2360 | 2394 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2361 | 2395 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2362 | _ = t; | |
| 2363 | _ = b; | |
| 2364 | return; | |
| 2396 | const operations = b.operations; | |
| 2397 | const len: u31 = @intCast(operations.len); | |
| 2398 | const ring = b.ring[0..len]; | |
| 2399 | var submit_head = b.impl.submit_head; | |
| 2400 | const submit_tail = b.user.submit_tail; | |
| 2401 | b.impl.submit_tail = submit_tail; | |
| 2402 | var complete_tail = b.impl.complete_tail; | |
| 2403 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | |
| 2404 | const op = ring[submit_head.index(len)]; | |
| 2405 | switch (operations[op]) { | |
| 2406 | .noop => { | |
| 2407 | operate(t, &operations[op]) catch unreachable; | |
| 2408 | ring[complete_tail.index(len)] = op; | |
| 2409 | complete_tail = complete_tail.next(len); | |
| 2410 | }, | |
| 2411 | .file_read_streaming => |*o| _ = o.status.unstarted, | |
| 2412 | } | |
| 2413 | } | |
| 2414 | b.impl.submit_head = submit_tail; | |
| 2415 | b.impl.complete_tail = complete_tail; | |
| 2416 | b.user.complete_tail = complete_tail; | |
| 2365 | 2417 | } |
| 2366 | 2418 | |
| 2367 | 2419 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { |
| ... | ... | @@ -9910,6 +9962,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 9910 | 9962 | |
| 9911 | 9963 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 9912 | 9964 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9965 | if (timeout == .none) return; | |
| 9913 | 9966 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| 9914 | 9967 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 9915 | 9968 | 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 | } |