| 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,9 +148,8 @@ pub const VTable = struct { |
| 148 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, | 148 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 149 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, | 149 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 150 | 150 | ||
| 151 | batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, | 151 | operate: *const fn (?*anyopaque, *Operation) Cancelable!void, |
| 152 | batchSubmit: *const fn (?*anyopaque, *Batch) void, | 152 | batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, |
| 153 | batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, | ||
| 154 | batchCancel: *const fn (?*anyopaque, *Batch) void, | 153 | batchCancel: *const fn (?*anyopaque, *Batch) void, |
| 155 | 154 | ||
| 156 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, | 155 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| ... | @@ -252,48 +251,51 @@ pub const Operation = union(enum) { | ... | @@ -252,48 +251,51 @@ pub const Operation = union(enum) { |
| 252 | 251 | ||
| 253 | pub const Noop = struct { | 252 | pub const Noop = struct { |
| 254 | reserved: [2]usize = .{ 0, 0 }, | 253 | reserved: [2]usize = .{ 0, 0 }, |
| 255 | status: Status(void) = .{ .result = {} }, | 254 | status: Status(void) = .{ .unstarted = {} }, |
| 256 | }; | 255 | }; |
| 257 | 256 | ||
| 258 | /// Returns 0 on end of stream. | 257 | /// Returns 0 on end of stream. |
| 259 | pub const FileReadStreaming = struct { | 258 | pub const FileReadStreaming = struct { |
| 260 | file: File, | 259 | file: File, |
| 261 | data: []const []u8, | 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 | pub fn Status(Result: type) type { | 287 | pub fn Status(Result: type) type { |
| 266 | return union { | 288 | return union { |
| 267 | unstarted: void, | 289 | unstarted: void, |
| 268 | pending: usize, | 290 | pending: *Batch, |
| 269 | result: Result, | 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 | /// Performs one `Operation`. | 296 | /// Performs one `Operation`. |
| 295 | pub fn operate(io: Io, operation: *Operation) void { | 297 | pub fn operate(io: Io, operation: *Operation) Cancelable!void { |
| 296 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | 298 | return io.vtable.operate(io.userdata, operation) catch unreachable; |
| 297 | } | 299 | } |
| 298 | 300 | ||
| 299 | /// Submits many operations together without waiting for all of them to | 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,35 +305,107 @@ pub fn operate(io: Io, operation: *Operation) void { |
| 303 | /// level API that operates on `Future`, see `Select`. | 305 | /// level API that operates on `Future`, see `Select`. |
| 304 | pub const Batch = struct { | 306 | pub const Batch = struct { |
| 305 | operations: []Operation, | 307 | operations: []Operation, |
| 306 | index: usize, | 308 | ring: [*]u32, |
| 307 | reserved: ?*anyopaque, | 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 { | 347 | pub fn init(operations: []Operation, ring: []u32) Batch { |
| 310 | return .{ .operations = operations, .index = 0, .reserved = null }; | 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`. | 367 | /// Adds `b.operations[operation]` to the list of submitted operations |
| 314 | pub fn submit(b: *Batch, io: Io) void { | 368 | /// that will be performed when `wait` is called. |
| 315 | return io.vtable.batchSubmit(io.userdata, b); | 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 | 380 | /// Returns `operation` such that `b.operations[operation]` has completed. |
| 321 | /// indexes given by `resubmissions`. This set of indexes typically will be empty | 381 | /// Returns `null` when `wait` should be called. |
| 322 | /// on the first call to `await` since all operations have already been | 382 | pub fn next(b: *Batch) ?u32 { |
| 323 | /// submitted via `async`. | 383 | const head = b.user.complete_head; |
| 324 | /// | 384 | if (head == b.user.complete_tail) { |
| 325 | /// Returns the index of a completed `Operation`, or `operations.len` if | 385 | @branchHint(.unlikely); |
| 326 | /// all operations are completed. | 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. | 402 | /// Returns `error.Timeout` if `timeout` expires first. |
| 329 | pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { | 403 | pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { |
| 330 | return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); | 404 | return io.vtable.batchWait(io.userdata, b, timeout); |
| 331 | } | 405 | } |
| 332 | 406 | ||
| 333 | /// Returns after all `operations` have completed. Each operation | 407 | /// Returns after all `operations` have completed. Operations which have not completed |
| 334 | /// independently may or may not have been canceled. | 408 | /// after this function returns were successfully dropped and had no side effects. |
| 335 | pub fn cancel(b: *Batch, io: Io) void { | 409 | pub fn cancel(b: *Batch, io: Io) void { |
| 336 | return io.vtable.batchCancel(io.userdata, b); | 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,7 +530,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 530 | .file = file, | 530 | .file = file, |
| 531 | .data = buffer, | 531 | .data = buffer, |
| 532 | } }; | 532 | } }; |
| 533 | io.operate(&operation); | 533 | try io.operate(&operation); |
| 534 | return operation.file_read_streaming.status.result; | 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,28 +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 | 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; | ||
| 51 | 30 | ||
| 52 | pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ | 31 | pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ |
| 53 | /// 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| ... | @@ -1438,8 +1438,7 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1438,8 +1438,7 @@ pub fn io(t: *Threaded) Io { |
| 1438 | .futexWaitUncancelable = futexWaitUncancelable, | 1438 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1439 | .futexWake = futexWake, | 1439 | .futexWake = futexWake, |
| 1440 | 1440 | ||
| 1441 | .batch = batch, | 1441 | .operate = operate, |
| 1442 | .batchSubmit = batchSubmit, | ||
| 1443 | .batchWait = batchWait, | 1442 | .batchWait = batchWait, |
| 1444 | .batchCancel = batchCancel, | 1443 | .batchCancel = batchCancel, |
| 1445 | 1444 | ||
| ... | @@ -1594,8 +1593,7 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1594,8 +1593,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1594 | .futexWaitUncancelable = futexWaitUncancelable, | 1593 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1595 | .futexWake = futexWake, | 1594 | .futexWake = futexWake, |
| 1596 | 1595 | ||
| 1597 | .batch = batch, | 1596 | .operate = operate, |
| 1598 | .batchSubmit = batchSubmit, | ||
| 1599 | .batchWait = batchWait, | 1597 | .batchWait = batchWait, |
| 1600 | .batchCancel = batchCancel, | 1598 | .batchCancel = batchCancel, |
| 1601 | 1599 | ||
| ... | @@ -2275,59 +2273,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -2275,59 +2273,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2275 | Thread.futexWake(ptr, max_waiters); | 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 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2277 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2280 | _ = t; | 2278 | _ = t; |
| 2281 | _ = b; | ||
| 2282 | return; | ||
| 2283 | } | ||
| 2284 | |||
| 2285 | fn operate(op: *Io.Operation) void { | ||
| 2286 | switch (op.*) { | 2279 | switch (op.*) { |
| 2287 | .noop => {}, | 2280 | .noop => |*o| { |
| 2288 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | 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( | 2294 | fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { |
| 2293 | userdata: ?*anyopaque, | ||
| 2294 | b: *Io.Batch, | ||
| 2295 | resubmissions: []const usize, | ||
| 2296 | timeout: Io.Timeout, | ||
| 2297 | ) Io.Batch.WaitError!usize { | ||
| 2298 | _ = resubmissions; | ||
| 2299 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2295 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2300 | const operations = b.operations; | 2296 | const operations = b.operations; |
| 2301 | if (operations.len == 1) { | 2297 | const len: u31 = @intCast(operations.len); |
| 2302 | operate(&operations[0]); | 2298 | const ring = b.ring[0..len]; |
| 2303 | return b.operations.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 | if (is_windows) @panic("TODO"); | 2314 | if (is_windows) @panic("TODO"); |
| 2306 | |||
| 2307 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | 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 | 2316 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { |
| 2309 | var poll_i: usize = 0; | 2317 | const op = ring[submit_head.index(len)]; |
| 2310 | 2318 | const operation = &operations[op]; | |
| 2311 | for (operations, 0..) |*op, operation_index| switch (op.*) { | 2319 | switch (operation.*) { |
| 2312 | .noop => continue, | 2320 | else => { |
| 2313 | .file_read_streaming => |*o| { | 2321 | try operate(t, operation); |
| 2314 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | 2322 | ring[complete_tail.index(len)] = op; |
| 2315 | poll_buffer[poll_i] = .{ | 2323 | complete_tail = complete_tail.next(len); |
| 2316 | .fd = o.file.handle, | 2324 | }, |
| 2317 | .events = posix.POLL.IN, | 2325 | .file_read_streaming => |*o| { |
| 2318 | .revents = 0, | 2326 | _ = o.status.unstarted; |
| 2319 | }; | 2327 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; |
| 2320 | map_buffer[poll_i] = @intCast(operation_index); | 2328 | poll_buffer[poll_i] = .{ |
| 2321 | poll_i += 1; | 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 | }; | 2347 | else => {}, |
| 2324 | 2348 | } | |
| 2325 | if (poll_i == 0) return operations.len; | ||
| 2326 | |||
| 2327 | const t_io = ioBasic(t); | 2349 | const t_io = ioBasic(t); |
| 2328 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; | 2350 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2329 | const max_poll_ms = std.math.maxInt(i32); | 2351 | const max_poll_ms = std.math.maxInt(i32); |
| 2330 | |||
| 2331 | while (true) { | 2352 | while (true) { |
| 2332 | const timeout_ms: i32 = if (deadline) |d| t: { | 2353 | const timeout_ms: i32 = if (deadline) |d| t: { |
| 2333 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; | 2354 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| ... | @@ -2345,11 +2366,24 @@ fn batchWait( | ... | @@ -2345,11 +2366,24 @@ fn batchWait( |
| 2345 | if (deadline == null) continue; | 2366 | if (deadline == null) continue; |
| 2346 | return error.Timeout; | 2367 | return error.Timeout; |
| 2347 | } | 2368 | } |
| 2348 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | 2369 | var canceled = false; |
| 2349 | if (poll_fd.revents == 0) continue; | 2370 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { |
| 2350 | operate(&operations[i]); | 2371 | if (poll_fd.revents == 0) { |
| 2351 | return i; | 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 | .INTR => continue, | 2388 | .INTR => continue, |
| 2355 | else => return error.ConcurrencyUnavailable, | 2389 | else => return error.ConcurrencyUnavailable, |
| ... | @@ -2359,9 +2393,27 @@ fn batchWait( | ... | @@ -2359,9 +2393,27 @@ fn batchWait( |
| 2359 | 2393 | ||
| 2360 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { | 2394 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2361 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2395 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2362 | _ = t; | 2396 | const operations = b.operations; |
| 2363 | _ = b; | 2397 | const len: u31 = @intCast(operations.len); |
| 2364 | return; | 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 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { | 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,6 +9962,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 9910 | 9962 | ||
| 9911 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | 9963 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 9912 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 9964 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9965 | if (timeout == .none) return; | ||
| 9913 | if (use_parking_sleep) return parking_sleep.sleep(timeout); | 9966 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| 9914 | if (native_os == .wasi) return sleepWasi(t, timeout); | 9967 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 9915 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); | 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,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 | } |