| author | |
| committer | |
| log | 1ca398b267298b5d03ce854459f2d5234fbf43ee |
| tree | 28ae584ded70ef43eb60ecef6b27f40f5678c3ec |
| parent | be2b93a073246eba1d2f44daeb1962b062096187 |
5 files changed, 313 insertions(+), 149 deletions(-)
lib/std/Io.zig+84-11| ... | @@ -148,7 +148,10 @@ pub const VTable = struct { | ... | @@ -148,7 +148,10 @@ 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 | operate: *const fn (?*anyopaque, []Operation) void, | 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, | ||
| 154 | batchCancel: *const fn (?*anyopaque, *Batch) void, | ||
| 152 | 155 | ||
| 153 | 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, |
| 154 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, | 157 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, |
| ... | @@ -244,26 +247,96 @@ pub const VTable = struct { | ... | @@ -244,26 +247,96 @@ pub const VTable = struct { |
| 244 | }; | 247 | }; |
| 245 | 248 | ||
| 246 | pub const Operation = union(enum) { | 249 | pub const Operation = union(enum) { |
| 247 | noop, | 250 | noop: Noop, |
| 248 | file_read_streaming: FileReadStreaming, | 251 | file_read_streaming: FileReadStreaming, |
| 249 | 252 | ||
| 253 | pub const Noop = struct { | ||
| 254 | reserved: [2]usize, | ||
| 255 | status: Status(void) = .{ .result = {} }, | ||
| 256 | }; | ||
| 257 | |||
| 258 | /// Returns 0 on end of stream. | ||
| 250 | pub const FileReadStreaming = struct { | 259 | pub const FileReadStreaming = struct { |
| 251 | file: File, | 260 | file: File, |
| 252 | data: []const []u8, | 261 | data: []const []u8, |
| 253 | /// Causes `result` to return `error.WouldBlock` instead of blocking. | 262 | status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, |
| 254 | nonblocking: bool = false, | ||
| 255 | /// Returns 0 on end of stream. | ||
| 256 | result: File.Reader.Error!usize, | ||
| 257 | }; | 263 | }; |
| 264 | |||
| 265 | pub fn Status(Result: type) type { | ||
| 266 | return union { | ||
| 267 | unstarted: void, | ||
| 268 | pending: usize, | ||
| 269 | result: Result, | ||
| 270 | }; | ||
| 271 | } | ||
| 258 | }; | 272 | }; |
| 259 | 273 | ||
| 260 | /// Performs all `operations` in a non-deterministic order. Returns after all | 274 | /// Performs all `operations` in an unspecified order, concurrently. |
| 261 | /// `operations` have been completed. The degree to which the operations are | 275 | /// |
| 262 | /// performed concurrently is determined by the `Io` implementation. | 276 | /// Returns after all `operations` have been completed. If the operations could |
| 263 | pub fn operate(io: Io, operations: []Operation) void { | 277 | /// not be completed concurrently, returns `error.ConcurrencyUnavailable`. |
| 264 | return io.vtable.operate(io.userdata, operations); | 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`. | ||
| 295 | pub fn operate(io: Io, operation: *Operation) void { | ||
| 296 | return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; | ||
| 265 | } | 297 | } |
| 266 | 298 | ||
| 299 | /// Submits many operations together without waiting for all of them to | ||
| 300 | /// complete. | ||
| 301 | /// | ||
| 302 | /// This is a low-level abstraction based on `Operation`. For a higher | ||
| 303 | /// level API that operates on `Future`, see `Select`. | ||
| 304 | pub const Batch = struct { | ||
| 305 | operations: []Operation, | ||
| 306 | index: usize, | ||
| 307 | reserved: ?*anyopaque, | ||
| 308 | |||
| 309 | pub fn init(operations: []Operation) Batch { | ||
| 310 | return .{ .operations = operations, .index = 0, .reserved = null }; | ||
| 311 | } | ||
| 312 | |||
| 313 | /// Submits all non-noop `operations`. | ||
| 314 | pub fn submit(b: *Batch, io: Io) void { | ||
| 315 | return io.vtable.batchSubmit(io.userdata, b); | ||
| 316 | } | ||
| 317 | |||
| 318 | pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; | ||
| 319 | |||
| 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. | ||
| 327 | /// | ||
| 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); | ||
| 331 | } | ||
| 332 | |||
| 333 | /// Returns after all `operations` have completed. Each operation | ||
| 334 | /// independently may or may not have been canceled. | ||
| 335 | pub fn cancel(b: *Batch, io: Io) void { | ||
| 336 | return io.vtable.batchCancel(io.userdata, b); | ||
| 337 | } | ||
| 338 | }; | ||
| 339 | |||
| 267 | pub const Limit = enum(usize) { | 340 | pub const Limit = enum(usize) { |
| 268 | nothing = 0, | 341 | nothing = 0, |
| 269 | unlimited = math.maxInt(usize), | 342 | unlimited = math.maxInt(usize), |
lib/std/Io/File.zig+2-3| ... | @@ -529,10 +529,9 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz | ... | @@ -529,10 +529,9 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz |
| 529 | var operation: Io.Operation = .{ .file_read_streaming = .{ | 529 | var operation: Io.Operation = .{ .file_read_streaming = .{ |
| 530 | .file = file, | 530 | .file = file, |
| 531 | .data = buffer, | 531 | .data = buffer, |
| 532 | .result = undefined, | ||
| 533 | } }; | 532 | } }; |
| 534 | io.vtable.operate(io.userdata, (&operation)[0..1]); | 533 | io.operate(&operation); |
| 535 | return operation.file_read_streaming.result; | 534 | return operation.file_read_streaming.status.result; |
| 536 | } | 535 | } |
| 537 | 536 | ||
| 538 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; | 537 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; |
lib/std/Io/Threaded.zig+171-85| ... | @@ -1438,7 +1438,10 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1438,7 +1438,10 @@ pub fn io(t: *Threaded) Io { |
| 1438 | .futexWaitUncancelable = futexWaitUncancelable, | 1438 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1439 | .futexWake = futexWake, | 1439 | .futexWake = futexWake, |
| 1440 | 1440 | ||
| 1441 | .operate = operate, | 1441 | .batch = batch, |
| 1442 | .batchSubmit = batchSubmit, | ||
| 1443 | .batchWait = batchWait, | ||
| 1444 | .batchCancel = batchCancel, | ||
| 1442 | 1445 | ||
| 1443 | .dirCreateDir = dirCreateDir, | 1446 | .dirCreateDir = dirCreateDir, |
| 1444 | .dirCreateDirPath = dirCreateDirPath, | 1447 | .dirCreateDirPath = dirCreateDirPath, |
| ... | @@ -1591,7 +1594,10 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1591,7 +1594,10 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1591 | .futexWaitUncancelable = futexWaitUncancelable, | 1594 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1592 | .futexWake = futexWake, | 1595 | .futexWake = futexWake, |
| 1593 | 1596 | ||
| 1594 | .operate = operate, | 1597 | .batch = batch, |
| 1598 | .batchSubmit = batchSubmit, | ||
| 1599 | .batchWait = batchWait, | ||
| 1600 | .batchCancel = batchCancel, | ||
| 1595 | 1601 | ||
| 1596 | .dirCreateDir = dirCreateDir, | 1602 | .dirCreateDir = dirCreateDir, |
| 1597 | .dirCreateDirPath = dirCreateDirPath, | 1603 | .dirCreateDirPath = dirCreateDirPath, |
| ... | @@ -2269,107 +2275,187 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -2269,107 +2275,187 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2269 | Thread.futexWake(ptr, max_waiters); | 2275 | Thread.futexWake(ptr, max_waiters); |
| 2270 | } | 2276 | } |
| 2271 | 2277 | ||
| 2272 | fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { | 2278 | fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2273 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2279 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2274 | _ = t; | 2280 | _ = t; |
| 2281 | _ = b; | ||
| 2282 | return; | ||
| 2283 | } | ||
| 2284 | |||
| 2285 | fn operate(op: *Io.Operation) void { | ||
| 2286 | switch (op.*) { | ||
| 2287 | .noop => {}, | ||
| 2288 | .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, | ||
| 2289 | } | ||
| 2290 | } | ||
| 2275 | 2291 | ||
| 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; | ||
| 2299 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 2300 | const operations = b.operations; | ||
| 2301 | if (operations.len == 1) { | ||
| 2302 | operate(&operations[0]); | ||
| 2303 | return b.operations.len; | ||
| 2304 | } | ||
| 2276 | if (is_windows) @panic("TODO"); | 2305 | if (is_windows) @panic("TODO"); |
| 2277 | 2306 | ||
| 2278 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | 2307 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2279 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | 2308 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index |
| 2280 | var operation_index: usize = 0; | 2309 | var poll_i: usize = 0; |
| 2281 | 2310 | ||
| 2282 | while (operation_index < operations.len) { | 2311 | for (operations, 0..) |*op, operation_index| switch (op.*) { |
| 2283 | var poll_i: usize = 0; | 2312 | .noop => continue, |
| 2284 | while (operation_index < operations.len) : (operation_index += 1) { | 2313 | .file_read_streaming => |*o| { |
| 2285 | switch (operations[operation_index]) { | 2314 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; |
| 2286 | .noop => continue, | 2315 | poll_buffer[poll_i] = .{ |
| 2287 | .file_read_streaming => |*o| { | 2316 | .fd = o.file.handle, |
| 2288 | if (o.nonblocking) { | 2317 | .events = posix.POLL.IN, |
| 2289 | o.result = error.WouldBlock; | 2318 | .revents = 0, |
| 2290 | poll_buffer[poll_i] = .{ | 2319 | }; |
| 2291 | .fd = o.file.handle, | 2320 | map_buffer[poll_i] = @intCast(operation_index); |
| 2292 | .events = posix.POLL.IN, | 2321 | poll_i += 1; |
| 2293 | .revents = 0, | 2322 | }, |
| 2294 | }; | 2323 | }; |
| 2295 | if (map_buffer.len - poll_i == 0) break; | ||
| 2296 | map_buffer[poll_i] = @intCast(operation_index); | ||
| 2297 | poll_i += 1; | ||
| 2298 | } else { | ||
| 2299 | o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { | ||
| 2300 | error.Canceled => { | ||
| 2301 | setOperationsError(operations[operation_index..], error.Canceled); | ||
| 2302 | return; | ||
| 2303 | }, | ||
| 2304 | else => err, | ||
| 2305 | }; | ||
| 2306 | } | ||
| 2307 | }, | ||
| 2308 | } | ||
| 2309 | } | ||
| 2310 | 2324 | ||
| 2311 | if (poll_i == 0) { | 2325 | if (poll_i == 0) return operations.len; |
| 2312 | @branchHint(.likely); | 2326 | |
| 2313 | return; | 2327 | const t_io = ioBasic(t); |
| 2328 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; | ||
| 2329 | const max_poll_ms = std.math.maxInt(i32); | ||
| 2330 | |||
| 2331 | while (true) { | ||
| 2332 | const timeout_ms: i32 = if (deadline) |d| t: { | ||
| 2333 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; | ||
| 2334 | if (duration.raw.nanoseconds <= 0) return error.Timeout; | ||
| 2335 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); | ||
| 2336 | } else -1; | ||
| 2337 | const syscall = try Syscall.start(); | ||
| 2338 | const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); | ||
| 2339 | syscall.finish(); | ||
| 2340 | switch (posix.errno(rc)) { | ||
| 2341 | .SUCCESS => { | ||
| 2342 | if (rc == 0) { | ||
| 2343 | // Although spurious timeouts are OK, when no deadline is | ||
| 2344 | // passed we must not return `error.Timeout`. | ||
| 2345 | if (deadline == null) continue; | ||
| 2346 | return error.Timeout; | ||
| 2347 | } | ||
| 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; | ||
| 2352 | } | ||
| 2353 | }, | ||
| 2354 | .INTR => continue, | ||
| 2355 | else => return error.ConcurrencyUnavailable, | ||
| 2314 | } | 2356 | } |
| 2357 | } | ||
| 2358 | } | ||
| 2315 | 2359 | ||
| 2316 | while (true) { | 2360 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2317 | const syscall = Syscall.start() catch |err| switch (err) { | 2361 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2318 | error.Canceled => { | 2362 | _ = t; |
| 2319 | setPollOperationsError(operations, map_buffer[0..poll_i], error.Canceled); | 2363 | _ = b; |
| 2320 | setOperationsError(operations[operation_index..], error.Canceled); | 2364 | return; |
| 2321 | return; | 2365 | } |
| 2322 | }, | 2366 | |
| 2367 | fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { | ||
| 2368 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 2369 | _ = t; | ||
| 2370 | |||
| 2371 | if (operations.len == 1) { | ||
| 2372 | @branchHint(.likely); | ||
| 2373 | return operate(&operations[0]); | ||
| 2374 | } | ||
| 2375 | |||
| 2376 | if (is_windows) @panic("TODO"); | ||
| 2377 | |||
| 2378 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | ||
| 2379 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | ||
| 2380 | var poll_i: usize = 0; | ||
| 2381 | |||
| 2382 | for (operations, 0..) |*op, operation_index| switch (op.*) { | ||
| 2383 | .noop => continue, | ||
| 2384 | .file_read_streaming => |*o| { | ||
| 2385 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | ||
| 2386 | poll_buffer[poll_i] = .{ | ||
| 2387 | .fd = o.file.handle, | ||
| 2388 | .events = posix.POLL.IN, | ||
| 2389 | .revents = 0, | ||
| 2323 | }; | 2390 | }; |
| 2324 | const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); | 2391 | map_buffer[poll_i] = @intCast(operation_index); |
| 2325 | syscall.finish(); | 2392 | poll_i += 1; |
| 2326 | switch (posix.errno(poll_rc)) { | 2393 | }, |
| 2327 | .SUCCESS => { | 2394 | }; |
| 2328 | if (poll_rc == 0) { | 2395 | |
| 2329 | // Spurious timeout; handle same as INTR. | 2396 | const polls = poll_buffer[0..poll_i]; |
| 2330 | continue; | 2397 | const map = map_buffer[0..poll_i]; |
| 2331 | } | 2398 | |
| 2332 | for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | 2399 | var pending = poll_i; |
| 2333 | if (poll_fd.revents == 0) continue; | 2400 | while (pending > 1) { |
| 2334 | switch (operations[i]) { | 2401 | const syscall = Syscall.start() catch |err| switch (err) { |
| 2335 | .noop => unreachable, | 2402 | error.Canceled => { |
| 2336 | .file_read_streaming => |*o| { | 2403 | if (!setOperationsError(operations, polls, map, error.Canceled)) |
| 2337 | o.result = fileReadStreaming(o.file, o.data); | 2404 | recancelInner(); |
| 2338 | }, | 2405 | return; |
| 2339 | } | 2406 | }, |
| 2340 | } | 2407 | }; |
| 2341 | break; | 2408 | const rc = posix.system.poll(polls.ptr, polls.len, -1); |
| 2342 | }, | 2409 | syscall.finish(); |
| 2343 | .INTR => continue, | 2410 | switch (posix.errno(rc)) { |
| 2344 | .NOMEM => { | 2411 | .SUCCESS => { |
| 2345 | setPollOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); | 2412 | if (rc == 0) { |
| 2346 | break; | 2413 | // Spurious timeout; handle the same as INTR. |
| 2347 | }, | 2414 | continue; |
| 2348 | else => { | 2415 | } |
| 2349 | setPollOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); | 2416 | for (polls, map) |*poll_fd, i| { |
| 2350 | break; | 2417 | if (poll_fd.revents == 0) continue; |
| 2351 | }, | 2418 | poll_fd.fd = -1; |
| 2352 | } | 2419 | pending -= 1; |
| 2420 | operate(&operations[i]); | ||
| 2421 | } | ||
| 2422 | }, | ||
| 2423 | .INTR => continue, | ||
| 2424 | .NOMEM => { | ||
| 2425 | assert(setOperationsError(operations, polls, map, error.SystemResources)); | ||
| 2426 | return; | ||
| 2427 | }, | ||
| 2428 | else => { | ||
| 2429 | assert(setOperationsError(operations, polls, map, error.Unexpected)); | ||
| 2430 | return; | ||
| 2431 | }, | ||
| 2353 | } | 2432 | } |
| 2354 | } | 2433 | } |
| 2434 | |||
| 2435 | if (pending == 1) for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { | ||
| 2436 | if (poll_fd.fd == -1) continue; | ||
| 2437 | operate(&operations[i]); | ||
| 2438 | }; | ||
| 2355 | } | 2439 | } |
| 2356 | 2440 | ||
| 2357 | fn setPollOperationsError( | 2441 | fn setOperationsError( |
| 2358 | operations: []Io.Operation, | 2442 | operations: []Io.Operation, |
| 2443 | polls: []const posix.pollfd, | ||
| 2359 | map: []const u8, | 2444 | map: []const u8, |
| 2360 | err: error{ Canceled, SystemResources, Unexpected }, | 2445 | err: error{ Canceled, SystemResources, Unexpected }, |
| 2361 | ) void { | 2446 | ) bool { |
| 2362 | for (map) |operation_index| switch (operations[operation_index]) { | 2447 | var marked = false; |
| 2363 | .noop => unreachable, | 2448 | for (polls, map) |*poll_fd, i| { |
| 2364 | inline else => |*o| o.result = err, | 2449 | if (poll_fd.fd == -1) continue; |
| 2365 | }; | 2450 | switch (operations[i]) { |
| 2366 | } | 2451 | .noop => unreachable, |
| 2367 | 2452 | inline else => |*o| { | |
| 2368 | fn setOperationsError(operations: []Io.Operation, err: error{ Canceled, SystemResources, Unexpected }) void { | 2453 | o.status = .{ .result = err }; |
| 2369 | for (operations) |*op| switch (op.*) { | 2454 | marked = true; |
| 2370 | .noop => unreachable, | 2455 | }, |
| 2371 | inline else => |*o| o.result = err, | 2456 | } |
| 2372 | }; | 2457 | } |
| 2458 | return marked; | ||
| 2373 | } | 2459 | } |
| 2374 | 2460 | ||
| 2375 | const dirCreateDir = switch (native_os) { | 2461 | const dirCreateDir = switch (native_os) { |
lib/std/process.zig+9-5| ... | @@ -465,9 +465,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { | ... | @@ -465,9 +465,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { |
| 465 | return io.vtable.processSpawnPath(io.userdata, dir, options); | 465 | return io.vtable.processSpawnPath(io.userdata, dir, options); |
| 466 | } | 466 | } |
| 467 | 467 | ||
| 468 | pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{ | 468 | pub const RunError = SpawnError || Child.CollectOutputError; |
| 469 | StreamTooLong, | ||
| 470 | }; | ||
| 471 | 469 | ||
| 472 | pub const RunOptions = struct { | 470 | pub const RunOptions = struct { |
| 473 | argv: []const []const u8, | 471 | argv: []const []const u8, |
| ... | @@ -545,9 +543,15 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { | ... | @@ -545,9 +543,15 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { |
| 545 | .stderr_limit = options.stderr_limit, | 543 | .stderr_limit = options.stderr_limit, |
| 546 | }); | 544 | }); |
| 547 | 545 | ||
| 546 | const stdout_slice = try stdout.toOwnedSlice(gpa); | ||
| 547 | errdefer gpa.free(stdout_slice); | ||
| 548 | |||
| 549 | const stderr_slice = try stderr.toOwnedSlice(gpa); | ||
| 550 | errdefer gpa.free(stderr_slice); | ||
| 551 | |||
| 548 | return .{ | 552 | return .{ |
| 549 | .stdout = try stdout.toOwnedSlice(gpa), | 553 | .stdout = stdout_slice, |
| 550 | .stderr = try stderr.toOwnedSlice(gpa), | 554 | .stderr = stderr_slice, |
| 551 | .term = try child.wait(io), | 555 | .term = try child.wait(io), |
| 552 | }; | 556 | }; |
| 553 | } | 557 | } |
lib/std/process/Child.zig+47-45| ... | @@ -125,7 +125,9 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { | ... | @@ -125,7 +125,9 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { |
| 125 | return io.vtable.childWait(io.userdata, child); | 125 | return io.vtable.childWait(io.userdata, child); |
| 126 | } | 126 | } |
| 127 | 127 | ||
| 128 | pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; | 128 | pub const CollectOutputError = error{ |
| 129 | StreamTooLong, | ||
| 130 | } || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; | ||
| 129 | 131 | ||
| 130 | pub const CollectOutputOptions = struct { | 132 | pub const CollectOutputOptions = struct { |
| 131 | stdout: *std.ArrayList(u8), | 133 | stdout: *std.ArrayList(u8), |
| ... | @@ -135,6 +137,7 @@ pub const CollectOutputOptions = struct { | ... | @@ -135,6 +137,7 @@ pub const CollectOutputOptions = struct { |
| 135 | allocator: ?Allocator = null, | 137 | allocator: ?Allocator = null, |
| 136 | stdout_limit: Io.Limit = .unlimited, | 138 | stdout_limit: Io.Limit = .unlimited, |
| 137 | stderr_limit: Io.Limit = .unlimited, | 139 | stderr_limit: Io.Limit = .unlimited, |
| 140 | timeout: Io.Timeout = .none, | ||
| 138 | }; | 141 | }; |
| 139 | 142 | ||
| 140 | /// Collect the output from the process's stdout and stderr. Will return once | 143 | /// Collect the output from the process's stdout and stderr. Will return once |
| ... | @@ -144,56 +147,55 @@ pub const CollectOutputOptions = struct { | ... | @@ -144,56 +147,55 @@ pub const CollectOutputOptions = struct { |
| 144 | /// The process must have been started with stdout and stderr set to | 147 | /// The process must have been started with stdout and stderr set to |
| 145 | /// `process.SpawnOptions.StdIo.pipe`. | 148 | /// `process.SpawnOptions.StdIo.pipe`. |
| 146 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { | 149 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { |
| 147 | const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; | ||
| 148 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; | 150 | const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; |
| 149 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; | 151 | const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; |
| 150 | var dones: [2]bool = .{ false, false }; | 152 | |
| 151 | var reads: [2]Io.Operation = undefined; | 153 | if (options.allocator) |gpa| { |
| 154 | for (lists) |list| try list.ensureUnusedCapacity(gpa, 1); | ||
| 155 | } else { | ||
| 156 | for (lists) |list| { | ||
| 157 | if (list.unusedCapacitySlice().len == 0) | ||
| 158 | return error.StreamTooLong; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 152 | var vecs: [2][1][]u8 = undefined; | 162 | var vecs: [2][1][]u8 = undefined; |
| 153 | while (true) { | 163 | for (lists, &vecs) |list, *vec| |
| 154 | for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { | 164 | vec[0] = list.unusedCapacitySlice(); |
| 155 | if (done) { | 165 | |
| 156 | read.* = .noop; | 166 | var operations: [2]Io.Operation = .{ |
| 157 | continue; | 167 | .{ .file_read_streaming = .{ |
| 158 | } | 168 | .file = child.stdout.?, |
| 169 | .data = &vecs[0], | ||
| 170 | } }, | ||
| 171 | .{ .file_read_streaming = .{ | ||
| 172 | .file = child.stderr.?, | ||
| 173 | .data = &vecs[1], | ||
| 174 | } }, | ||
| 175 | }; | ||
| 176 | |||
| 177 | var batch: Io.Batch = .init(&operations); | ||
| 178 | batch.submit(io); | ||
| 179 | defer batch.cancel(io); | ||
| 180 | |||
| 181 | var pending = operations.len; | ||
| 182 | var retry_index: ?usize = null; | ||
| 183 | while (pending > 0) { | ||
| 184 | const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{}; | ||
| 185 | const index = try batch.wait(io, resubmissions, options.timeout); | ||
| 186 | const n = try operations[index].file_read_streaming.status.result; | ||
| 187 | if (n == 0) { | ||
| 188 | pending -= 1; | ||
| 189 | } else { | ||
| 190 | retry_index = index; | ||
| 191 | const list = lists[index]; | ||
| 192 | const limit = limits[index]; | ||
| 193 | list.items.len += n; | ||
| 194 | if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong; | ||
| 159 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); | 195 | if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); |
| 160 | const cap = list.unusedCapacitySlice(); | 196 | const cap = list.unusedCapacitySlice(); |
| 161 | if (cap.len == 0) return error.StreamTooLong; | 197 | if (cap.len == 0) return error.StreamTooLong; |
| 162 | vec[0] = cap; | 198 | vecs[index][0] = cap; |
| 163 | read.* = .{ .file_read_streaming = .{ | ||
| 164 | .file = file, | ||
| 165 | .data = vec, | ||
| 166 | .nonblocking = true, | ||
| 167 | .result = undefined, | ||
| 168 | } }; | ||
| 169 | } | ||
| 170 | var all_done = true; | ||
| 171 | var any_canceled = false; | ||
| 172 | var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; | ||
| 173 | io.vtable.operate(io.userdata, &reads); | ||
| 174 | for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { | ||
| 175 | if (done.*) continue; | ||
| 176 | const n = read.file_read_streaming.result catch |err| switch (err) { | ||
| 177 | error.Canceled => { | ||
| 178 | any_canceled = true; | ||
| 179 | continue; | ||
| 180 | }, | ||
| 181 | error.WouldBlock => continue, | ||
| 182 | else => |e| { | ||
| 183 | other_err = e; | ||
| 184 | continue; | ||
| 185 | }, | ||
| 186 | }; | ||
| 187 | if (n == 0) { | ||
| 188 | done.* = true; | ||
| 189 | } else { | ||
| 190 | all_done = false; | ||
| 191 | } | ||
| 192 | list.items.len += n; | ||
| 193 | if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; | ||
| 194 | } | 199 | } |
| 195 | if (any_canceled) return error.Canceled; | ||
| 196 | try other_err; | ||
| 197 | if (all_done) return; | ||
| 198 | } | 200 | } |
| 199 | } | 201 | } |