| ... | @@ -1617,7 +1617,8 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1617,7 +1617,8 @@ pub fn io(t: *Threaded) Io { |
| 1617 | .futexWake = futexWake, | 1617 | .futexWake = futexWake, |
| 1618 | | 1618 | |
| 1619 | .operate = operate, | 1619 | .operate = operate, |
| 1620 | .batchWait = batchWait, | 1620 | .batchAwaitAsync = batchAwaitAsync, |
| | 1621 | .batchAwaitConcurrent = batchAwaitConcurrent, |
| 1621 | .batchCancel = batchCancel, | 1622 | .batchCancel = batchCancel, |
| 1622 | | 1623 | |
| 1623 | .dirCreateDir = dirCreateDir, | 1624 | .dirCreateDir = dirCreateDir, |
| ... | @@ -1780,7 +1781,8 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1780,7 +1781,8 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1780 | .futexWake = futexWake, | 1781 | .futexWake = futexWake, |
| 1781 | | 1782 | |
| 1782 | .operate = operate, | 1783 | .operate = operate, |
| 1783 | .batchWait = batchWait, | 1784 | .batchAwaitAsync = batchAwaitAsync, |
| | 1785 | .batchAwaitConcurrent = batchAwaitConcurrent, |
| 1784 | .batchCancel = batchCancel, | 1786 | .batchCancel = batchCancel, |
| 1785 | | 1787 | |
| 1786 | .dirCreateDir = dirCreateDir, | 1788 | .dirCreateDir = dirCreateDir, |
| ... | @@ -2483,85 +2485,227 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { | ... | @@ -2483,85 +2485,227 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2483 | Thread.futexWake(ptr, max_waiters); | 2485 | Thread.futexWake(ptr, max_waiters); |
| 2484 | } | 2486 | } |
| 2485 | | 2487 | |
| 2486 | fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { | 2488 | fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result { |
| 2487 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2489 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2488 | switch (op.*) { | 2490 | switch (operation) { |
| 2489 | .noop => |*o| { | 2491 | .file_read_streaming => |o| return .{ |
| 2490 | _ = o.status.unstarted; | 2492 | .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { |
| 2491 | o.status = .{ .result = {} }; | | |
| 2492 | }, | | |
| 2493 | .file_read_streaming => |*o| { | | |
| 2494 | _ = o.status.unstarted; | | |
| 2495 | o.status = .{ .result = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { | | |
| 2496 | error.Canceled => |e| return e, | 2493 | error.Canceled => |e| return e, |
| 2497 | else => |e| e, | 2494 | else => |e| e, |
| 2498 | } }; | 2495 | }, |
| 2499 | }, | 2496 | }, |
| 2500 | } | 2497 | } |
| 2501 | } | 2498 | } |
| 2502 | | 2499 | |
| 2503 | fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { | 2500 | fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Batch.AwaitAsyncError!void { |
| 2504 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2501 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2505 | if (is_windows) return batchWaitWindows(t, b, timeout); | 2502 | if (is_windows) { |
| | 2503 | try batchAwaitWindows(b); |
| | 2504 | const alertable_syscall = try AlertableSyscall.start(); |
| | 2505 | while (b.pending.head != .none and b.completions.head == .none) waitForApcOrAlert(); |
| | 2506 | alertable_syscall.finish(); |
| | 2507 | return; |
| | 2508 | } |
| 2506 | if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); | 2509 | if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); |
| 2507 | const operations = b.operations; | 2510 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2508 | const len: u31 = @intCast(operations.len); | 2511 | var poll_len: u32 = 0; |
| 2509 | const ring = b.ring[0..len]; | 2512 | { |
| 2510 | var submit_head = b.impl.submit_head; | 2513 | var index = b.submissions.head; |
| 2511 | const submit_tail = b.user.submit_tail; | 2514 | while (index != .none and poll_len < poll_buffer_len) { |
| 2512 | b.impl.submit_tail = submit_tail; | 2515 | const submission = &b.storage[index.toIndex()].submission; |
| 2513 | var complete_tail = b.impl.complete_tail; | 2516 | switch (submission.operation) { |
| 2514 | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index | 2517 | .file_read_streaming => |o| { |
| 2515 | var poll_i: u8 = 0; | 2518 | poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; |
| 2516 | defer { | 2519 | poll_len += 1; |
| 2517 | for (map_buffer[0..poll_i]) |op| { | 2520 | }, |
| 2518 | submit_head = submit_head.prev(len); | 2521 | } |
| 2519 | ring[submit_head.index(len)] = op; | 2522 | index = submission.node.next; |
| 2520 | } | 2523 | } |
| 2521 | b.impl.submit_head = submit_head; | 2524 | } |
| 2522 | b.impl.complete_tail = complete_tail; | 2525 | switch (poll_len) { |
| 2523 | b.user.complete_tail = complete_tail; | 2526 | 0 => return, |
| | 2527 | 1 => {}, |
| | 2528 | else => while (true) { |
| | 2529 | const timeout_ms: i32 = t: { |
| | 2530 | if (b.completions.head != .none) { |
| | 2531 | // It is legal to call batchWait with already completed |
| | 2532 | // operations in the ring. In such case, we need to avoid |
| | 2533 | // blocking in the poll syscall, but we can still take this |
| | 2534 | // opportunity to find additional ready operations. |
| | 2535 | break :t 0; |
| | 2536 | } |
| | 2537 | const max_poll_ms = std.math.maxInt(i32); |
| | 2538 | break :t max_poll_ms; |
| | 2539 | }; |
| | 2540 | const syscall = try Syscall.start(); |
| | 2541 | const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); |
| | 2542 | syscall.finish(); |
| | 2543 | switch (posix.errno(rc)) { |
| | 2544 | .SUCCESS => { |
| | 2545 | if (rc == 0) { |
| | 2546 | if (b.completions.head != .none) { |
| | 2547 | // Since there are already completions available in the |
| | 2548 | // queue, this is neither a timeout nor a case for |
| | 2549 | // retrying. |
| | 2550 | return; |
| | 2551 | } |
| | 2552 | continue; |
| | 2553 | } |
| | 2554 | var prev_index: Io.Operation.OptionalIndex = .none; |
| | 2555 | var index = b.submissions.head; |
| | 2556 | for (poll_buffer[0..poll_len]) |poll_entry| { |
| | 2557 | const storage = &b.storage[index.toIndex()]; |
| | 2558 | const submission = &storage.submission; |
| | 2559 | const next_index = submission.node.next; |
| | 2560 | if (poll_entry.revents != 0) { |
| | 2561 | const result = try operate(t, submission.operation); |
| | 2562 | |
| | 2563 | switch (prev_index) { |
| | 2564 | .none => b.submissions.head = next_index, |
| | 2565 | else => b.storage[prev_index.toIndex()].submission.node.next = next_index, |
| | 2566 | } |
| | 2567 | if (next_index == .none) b.submissions.tail = prev_index; |
| | 2568 | |
| | 2569 | switch (b.completions.tail) { |
| | 2570 | .none => b.completions.head = index, |
| | 2571 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| | 2572 | } |
| | 2573 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| | 2574 | b.completions.tail = index; |
| | 2575 | } else prev_index = index; |
| | 2576 | index = next_index; |
| | 2577 | } |
| | 2578 | assert(index == .none); |
| | 2579 | return; |
| | 2580 | }, |
| | 2581 | .INTR => continue, |
| | 2582 | else => break, |
| | 2583 | } |
| | 2584 | }, |
| | 2585 | } |
| | 2586 | { |
| | 2587 | var tail_index = b.completions.tail; |
| | 2588 | defer b.completions.tail = tail_index; |
| | 2589 | var index = b.submissions.head; |
| | 2590 | errdefer b.submissions.head = index; |
| | 2591 | while (index != .none) { |
| | 2592 | const storage = &b.storage[index.toIndex()]; |
| | 2593 | const submission = &storage.submission; |
| | 2594 | const next_index = submission.node.next; |
| | 2595 | const result = try operate(t, submission.operation); |
| | 2596 | |
| | 2597 | switch (tail_index) { |
| | 2598 | .none => b.completions.head = index, |
| | 2599 | else => b.storage[tail_index.toIndex()].completion.node.next = index, |
| | 2600 | } |
| | 2601 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| | 2602 | tail_index = index; |
| | 2603 | index = next_index; |
| | 2604 | } |
| | 2605 | b.submissions = .{ .head = .none, .tail = .none }; |
| 2524 | } | 2606 | } |
| | 2607 | } |
| | 2608 | |
| | 2609 | fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { |
| | 2610 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| | 2611 | if (is_windows) { |
| | 2612 | const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { |
| | 2613 | error.Unexpected => deadline: { |
| | 2614 | recoverableOsBugDetected(); |
| | 2615 | break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; |
| | 2616 | }, |
| | 2617 | error.UnsupportedClock => |e| return e, |
| | 2618 | }; |
| | 2619 | try batchAwaitWindows(b); |
| | 2620 | while (b.pending.head != .none and b.completions.head == .none) { |
| | 2621 | var delay_interval: windows.LARGE_INTEGER = interval: { |
| | 2622 | const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); |
| | 2623 | break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { |
| | 2624 | error.UnsupportedClock => |e| return e, |
| | 2625 | error.Unexpected => { |
| | 2626 | recoverableOsBugDetected(); |
| | 2627 | break :interval -1; |
| | 2628 | }, |
| | 2629 | }; |
| | 2630 | }; |
| | 2631 | const alertable_syscall = try AlertableSyscall.start(); |
| | 2632 | const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); |
| | 2633 | alertable_syscall.finish(); |
| | 2634 | switch (delay_rc) { |
| | 2635 | .SUCCESS, .TIMEOUT => { |
| | 2636 | // The thread woke due to the timeout. Although spurious |
| | 2637 | // timeouts are OK, when no deadline is passed we must not |
| | 2638 | // return `error.Timeout`. |
| | 2639 | if (timeout != .none and b.completions.head == .none) return error.Timeout; |
| | 2640 | }, |
| | 2641 | else => {}, |
| | 2642 | } |
| | 2643 | } |
| | 2644 | return; |
| | 2645 | } |
| | 2646 | if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); |
| 2525 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; | 2647 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2526 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | 2648 | var poll_storage: struct { |
| 2527 | const op = ring[submit_head.index(len)]; | 2649 | gpa: std.mem.Allocator, |
| 2528 | const operation = &operations[op]; | 2650 | b: *Io.Batch, |
| 2529 | switch (operation.*) { | 2651 | slice: []posix.pollfd, |
| 2530 | .noop => |*o| { | 2652 | len: u32, |
| 2531 | _ = o.status.unstarted; | 2653 | |
| 2532 | o.status = .{ .result = {} }; | 2654 | fn add(storage: *@This(), file: Io.File, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void { |
| 2533 | submitComplete(ring, &complete_tail, op); | 2655 | const len = storage.len; |
| 2534 | }, | 2656 | if (len == poll_buffer_len) { |
| 2535 | .file_read_streaming => |*o| { | 2657 | const slice: []posix.pollfd = if (storage.b.context) |context| |
| 2536 | _ = o.status.unstarted; | 2658 | @as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..storage.b.storage.len] |
| 2537 | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; | 2659 | else allocation: { |
| 2538 | poll_buffer[poll_i] = .{ | 2660 | const allocation = storage.gpa.alloc(posix.pollfd, storage.b.storage.len) catch |
| 2539 | .fd = o.file.handle, | 2661 | return error.ConcurrencyUnavailable; |
| 2540 | .events = posix.POLL.IN, | 2662 | storage.b.context = allocation.ptr; |
| 2541 | .revents = 0, | 2663 | break :allocation allocation; |
| 2542 | }; | 2664 | }; |
| 2543 | map_buffer[poll_i] = @intCast(op); | 2665 | @memcpy(slice[0..poll_buffer_len], storage.slice); |
| 2544 | poll_i += 1; | 2666 | } |
| 2545 | }, | 2667 | storage.slice[len] = .{ |
| | 2668 | .fd = file.handle, |
| | 2669 | .events = events, |
| | 2670 | .revents = 0, |
| | 2671 | }; |
| | 2672 | storage.len = len + 1; |
| | 2673 | } |
| | 2674 | } = .{ .gpa = t.allocator, .b = b, .slice = &poll_buffer, .len = 0 }; |
| | 2675 | { |
| | 2676 | var index = b.submissions.head; |
| | 2677 | while (index != .none) { |
| | 2678 | const submission = &b.storage[index.toIndex()].submission; |
| | 2679 | switch (submission.operation) { |
| | 2680 | .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), |
| | 2681 | } |
| | 2682 | index = submission.node.next; |
| 2546 | } | 2683 | } |
| 2547 | } | 2684 | } |
| 2548 | switch (poll_i) { | 2685 | switch (poll_storage.len) { |
| 2549 | 0 => return, | 2686 | 0 => return, |
| 2550 | 1 => if (timeout == .none) { | 2687 | 1 => if (timeout == .none) { |
| 2551 | const op = map_buffer[0]; | 2688 | const index = b.submissions.head; |
| 2552 | try operate(t, &operations[op]); | 2689 | const storage = &b.storage[index.toIndex()]; |
| 2553 | submitComplete(ring, &complete_tail, op); | 2690 | const result = try operate(t, storage.submission.operation); |
| 2554 | poll_i = 0; | 2691 | |
| | 2692 | b.submissions = .{ .head = .none, .tail = .none }; |
| | 2693 | |
| | 2694 | switch (b.completions.tail) { |
| | 2695 | .none => b.completions.head = index, |
| | 2696 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| | 2697 | } |
| | 2698 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| | 2699 | b.completions.tail = index; |
| 2555 | return; | 2700 | return; |
| 2556 | }, | 2701 | }, |
| 2557 | else => {}, | 2702 | else => {}, |
| 2558 | } | 2703 | } |
| 2559 | const t_io = ioBasic(t); | 2704 | const t_io = ioBasic(t); |
| 2560 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; | 2705 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2561 | const max_poll_ms = std.math.maxInt(i32); | | |
| 2562 | while (true) { | 2706 | while (true) { |
| 2563 | const timeout_ms: i32 = t: { | 2707 | const timeout_ms: i32 = t: { |
| 2564 | if (b.user.complete_head != complete_tail) { | 2708 | if (b.completions.head != .none) { |
| 2565 | // It is legal to call batchWait with already completed | 2709 | // It is legal to call batchWait with already completed |
| 2566 | // operations in the ring. In such case, we need to avoid | 2710 | // operations in the ring. In such case, we need to avoid |
| 2567 | // blocking in the poll syscall, but we can still take this | 2711 | // blocking in the poll syscall, but we can still take this |
| ... | @@ -2571,15 +2715,16 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. | ... | @@ -2571,15 +2715,16 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. |
| 2571 | const d = deadline orelse break :t -1; | 2715 | const d = deadline orelse break :t -1; |
| 2572 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; | 2716 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| 2573 | if (duration.raw.nanoseconds <= 0) return error.Timeout; | 2717 | if (duration.raw.nanoseconds <= 0) return error.Timeout; |
| | 2718 | const max_poll_ms = std.math.maxInt(i32); |
| 2574 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); | 2719 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); |
| 2575 | }; | 2720 | }; |
| 2576 | const syscall = try Syscall.start(); | 2721 | const syscall = try Syscall.start(); |
| 2577 | const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); | 2722 | const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); |
| 2578 | syscall.finish(); | 2723 | syscall.finish(); |
| 2579 | switch (posix.errno(rc)) { | 2724 | switch (posix.errno(rc)) { |
| 2580 | .SUCCESS => { | 2725 | .SUCCESS => { |
| 2581 | if (rc == 0) { | 2726 | if (rc == 0) { |
| 2582 | if (b.user.complete_head != complete_tail) { | 2727 | if (b.completions.head != .none) { |
| 2583 | // Since there are already completions available in the | 2728 | // Since there are already completions available in the |
| 2584 | // queue, this is neither a timeout nor a case for | 2729 | // queue, this is neither a timeout nor a case for |
| 2585 | // retrying. | 2730 | // retrying. |
| ... | @@ -2590,18 +2735,30 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. | ... | @@ -2590,18 +2735,30 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. |
| 2590 | if (deadline == null) continue; | 2735 | if (deadline == null) continue; |
| 2591 | return error.Timeout; | 2736 | return error.Timeout; |
| 2592 | } | 2737 | } |
| 2593 | while (poll_i != 0) { | 2738 | var prev_index: Io.Operation.OptionalIndex = .none; |
| 2594 | poll_i -= 1; | 2739 | var index = b.submissions.head; |
| 2595 | const poll_fd = &poll_buffer[poll_i]; | 2740 | for (poll_storage.slice[0..poll_storage.len]) |poll_entry| { |
| 2596 | const op = map_buffer[poll_i]; | 2741 | const submission = &b.storage[index.toIndex()].submission; |
| 2597 | if (poll_fd.revents == 0) { | 2742 | const next_index = submission.node.next; |
| 2598 | submit_head = submit_head.prev(len); | 2743 | if (poll_entry.revents != 0) { |
| 2599 | ring[submit_head.index(len)] = op; | 2744 | const result = try operate(t, submission.operation); |
| 2600 | } else { | 2745 | |
| 2601 | try operate(t, &operations[op]); | 2746 | switch (prev_index) { |
| 2602 | submitComplete(ring, &complete_tail, op); | 2747 | .none => b.submissions.head = next_index, |
| 2603 | } | 2748 | else => b.storage[prev_index.toIndex()].submission.node.next = next_index, |
| | 2749 | } |
| | 2750 | if (next_index == .none) b.submissions.tail = prev_index; |
| | 2751 | |
| | 2752 | switch (b.completions.tail) { |
| | 2753 | .none => b.completions.head = index, |
| | 2754 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| | 2755 | } |
| | 2756 | b.completions.tail = index; |
| | 2757 | b.storage[index.toIndex()] = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| | 2758 | } else prev_index = index; |
| | 2759 | index = next_index; |
| 2604 | } | 2760 | } |
| | 2761 | assert(index == .none); |
| 2605 | return; | 2762 | return; |
| 2606 | }, | 2763 | }, |
| 2607 | .INTR => continue, | 2764 | .INTR => continue, |
| ... | @@ -2610,166 +2767,126 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. | ... | @@ -2610,166 +2767,126 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. |
| 2610 | } | 2767 | } |
| 2611 | } | 2768 | } |
| 2612 | | 2769 | |
| | 2770 | const WindowsBatchPendingOperationContext = extern struct { |
| | 2771 | file: windows.HANDLE, |
| | 2772 | iosb: windows.IO_STATUS_BLOCK, |
| | 2773 | |
| | 2774 | const Erased = [3]usize; |
| | 2775 | |
| | 2776 | comptime { |
| | 2777 | assert(@sizeOf(Erased) <= @sizeOf(WindowsBatchPendingOperationContext)); |
| | 2778 | } |
| | 2779 | |
| | 2780 | fn toErased(context: *WindowsBatchPendingOperationContext) *Erased { |
| | 2781 | return @ptrCast(context); |
| | 2782 | } |
| | 2783 | |
| | 2784 | fn fromErased(erased: *Erased) *WindowsBatchPendingOperationContext { |
| | 2785 | return @ptrCast(erased); |
| | 2786 | } |
| | 2787 | }; |
| | 2788 | |
| 2613 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { | 2789 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2614 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 2790 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2615 | const operations = b.operations; | 2791 | { |
| 2616 | const len: u31 = @intCast(operations.len); | 2792 | var tail_index = b.unused.tail; |
| 2617 | const ring = b.ring[0..len]; | 2793 | defer b.unused.tail = tail_index; |
| 2618 | var submit_head = b.impl.submit_head; | 2794 | var index = b.submissions.head; |
| 2619 | const submit_tail = b.user.submit_tail; | 2795 | errdefer b.submissions.head = index; |
| 2620 | b.impl.submit_tail = submit_tail; | 2796 | while (index != .none) { |
| 2621 | var complete_tail = b.impl.complete_tail; | 2797 | const next_index = b.storage[index.toIndex()].submission.node.next; |
| 2622 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | 2798 | switch (tail_index) { |
| 2623 | const op = ring[submit_head.index(len)]; | 2799 | .none => b.unused.head = index, |
| 2624 | switch (operations[op]) { | 2800 | else => b.storage[tail_index.toIndex()].unused.next = index, |
| 2625 | .noop => |*o| { | 2801 | } |
| 2626 | _ = o.status.unstarted; | 2802 | b.storage[index.toIndex()] = .{ .unused = .{ .prev = tail_index, .next = .none } }; |
| 2627 | o.status = .{ .result = {} }; | 2803 | tail_index = index; |
| 2628 | submitComplete(ring, &complete_tail, op); | 2804 | index = next_index; |
| 2629 | }, | | |
| 2630 | .file_read_streaming => |*o| _ = o.status.unstarted, | | |
| 2631 | } | 2805 | } |
| | 2806 | b.submissions = .{ .head = .none, .tail = .none }; |
| 2632 | } | 2807 | } |
| 2633 | if (is_windows) { | 2808 | if (is_windows) { |
| 2634 | // Iterate over pending and issue cancelations, then free the allocation for IO_STATUS_BLOCK | 2809 | var index = b.pending.head; |
| 2635 | if (b.impl.reserved) |reserved| { | 2810 | while (index != .none) { |
| 2636 | const gpa = t.allocator; | 2811 | const pending = &b.storage[index.toIndex()].pending; |
| 2637 | const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved)); | 2812 | const context: *WindowsBatchPendingOperationContext = .fromErased(&pending.context); |
| 2638 | const metadatas = metadatas_ptr[0..b.operations.len]; | 2813 | _ = windows.ntdll.NtCancelIoFile(context.file, &context.iosb); |
| 2639 | for (metadatas, 0..) |*metadata, op| { | 2814 | index = pending.node.next; |
| 2640 | if (!metadata.pending) continue; | 2815 | } |
| 2641 | const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; | 2816 | while (b.pending.head != .none) waitForApcOrAlert(); |
| 2642 | if (done) continue; | 2817 | } else if (b.context) |context| { |
| 2643 | switch (operations[op]) { | 2818 | t.allocator.free(@as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..b.storage.len]); |
| 2644 | .noop => unreachable, | 2819 | b.context = null; |
| 2645 | .file_read_streaming => |*o| { | 2820 | } |
| 2646 | _ = windows.ntdll.NtCancelIoFile(o.file.handle, &metadata.iosb); | 2821 | assert(b.pending.head == .none); |
| 2647 | }, | 2822 | } |
| 2648 | } | 2823 | |
| | 2824 | fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { |
| | 2825 | const b: *Io.Batch = @ptrCast(@alignCast(apc_context)); |
| | 2826 | const context: *WindowsBatchPendingOperationContext = @fieldParentPtr("iosb", iosb); |
| | 2827 | const erased_context = context.toErased(); |
| | 2828 | const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("context", erased_context); |
| | 2829 | switch (pending.node.prev) { |
| | 2830 | .none => b.pending.head = pending.node.next, |
| | 2831 | else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, |
| | 2832 | } |
| | 2833 | switch (pending.node.next) { |
| | 2834 | .none => b.pending.tail = pending.node.prev, |
| | 2835 | else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, |
| | 2836 | } |
| | 2837 | const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); |
| | 2838 | const index = storage - b.storage.ptr; |
| | 2839 | switch (iosb.u.Status) { |
| | 2840 | .CANCELLED => { |
| | 2841 | const tail_index = b.unused.tail; |
| | 2842 | switch (tail_index) { |
| | 2843 | .none => b.unused.head = .fromIndex(index), |
| | 2844 | else => b.storage[tail_index.toIndex()].unused.next = .fromIndex(index), |
| 2649 | } | 2845 | } |
| 2650 | for (metadatas) |*metadata| { | 2846 | storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; |
| 2651 | if (!metadata.pending) continue; | 2847 | b.unused.tail = .fromIndex(index); |
| 2652 | while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { | 2848 | }, |
| 2653 | waitForApcOrAlert(); | 2849 | else => { |
| 2654 | } | 2850 | switch (b.completions.tail) { |
| | 2851 | .none => b.completions.head = .fromIndex(index), |
| | 2852 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = .fromIndex(index), |
| 2655 | } | 2853 | } |
| 2656 | gpa.free(metadatas); | 2854 | b.completions.tail = .fromIndex(index); |
| 2657 | b.impl.reserved = null; | 2855 | const result: Io.Operation.Result = switch (pending.tag) { |
| 2658 | } | 2856 | .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, |
| | 2857 | }; |
| | 2858 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| | 2859 | }, |
| 2659 | } | 2860 | } |
| 2660 | b.impl.submit_head = submit_tail; | | |
| 2661 | b.impl.complete_tail = complete_tail; | | |
| 2662 | b.user.complete_tail = complete_tail; | | |
| 2663 | } | 2861 | } |
| 2664 | | 2862 | |
| 2665 | const WinOpMetadata = struct { | 2863 | fn batchAwaitWindows(b: *Io.Batch) Io.Cancelable!void { |
| 2666 | iosb: windows.IO_STATUS_BLOCK, | 2864 | var index = b.submissions.head; |
| 2667 | pending: bool, | 2865 | errdefer b.submissions.head = index; |
| 2668 | }; | 2866 | while (index != .none) { |
| 2669 | | 2867 | const storage = &b.storage[index.toIndex()]; |
| 2670 | fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { | 2868 | const submission = storage.submission; |
| 2671 | const operations = b.operations; | 2869 | errdefer storage.* = .{ .submission = submission }; |
| 2672 | const len: u31 = @intCast(operations.len); | 2870 | storage.* = .{ .pending = .{ |
| 2673 | const ring = b.ring[0..len]; | 2871 | .node = .{ .prev = b.pending.tail, .next = .none }, |
| 2674 | var submit_head = b.impl.submit_head; | 2872 | .tag = submission.operation, |
| 2675 | const submit_tail = b.user.submit_tail; | 2873 | .context = undefined, |
| 2676 | b.impl.submit_tail = submit_tail; | 2874 | } }; |
| 2677 | var complete_tail = b.impl.complete_tail; | 2875 | const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context); |
| 2678 | | 2876 | switch (submission.operation) { |
| 2679 | const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: { | 2877 | .file_read_streaming => |o| { |
| 2680 | const gpa = t.allocator; | 2878 | context.file = o.file.handle; |
| 2681 | const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable; | 2879 | try ntReadFile(o.file.handle, o.data, &batchApc, b, &context.iosb); |
| 2682 | b.impl.reserved = metadatas.ptr; | | |
| 2683 | @memset(metadatas, .{ .iosb = undefined, .pending = false }); | | |
| 2684 | break :a metadatas.ptr; | | |
| 2685 | }; | | |
| 2686 | const metadatas = metadatas_ptr[0..operations.len]; | | |
| 2687 | | | |
| 2688 | defer { | | |
| 2689 | b.impl.submit_head = submit_head; | | |
| 2690 | b.impl.complete_tail = complete_tail; | | |
| 2691 | b.user.complete_tail = complete_tail; | | |
| 2692 | } | | |
| 2693 | | | |
| 2694 | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { | | |
| 2695 | const op = ring[submit_head.index(len)]; | | |
| 2696 | const operation = &operations[op]; | | |
| 2697 | const metadata = &metadatas[op]; | | |
| 2698 | metadata.* = .{ .iosb = .{ | | |
| 2699 | .u = .{ .Status = .PENDING }, | | |
| 2700 | .Information = 0, | | |
| 2701 | }, .pending = false }; | | |
| 2702 | switch (operation.*) { | | |
| 2703 | .noop => |*o| { | | |
| 2704 | _ = o.status.unstarted; | | |
| 2705 | o.status = .{ .result = {} }; | | |
| 2706 | submitComplete(ring, &complete_tail, op); | | |
| 2707 | }, | | |
| 2708 | .file_read_streaming => |*o| { | | |
| 2709 | _ = o.status.unstarted; | | |
| 2710 | try ntReadFile(o.file.handle, o.data, &metadata.iosb); | | |
| 2711 | if (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { | | |
| 2712 | o.status = .{ .pending = b }; | | |
| 2713 | metadata.pending = true; | | |
| 2714 | } else { | | |
| 2715 | o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; | | |
| 2716 | submitComplete(ring, &complete_tail, op); | | |
| 2717 | } | | |
| 2718 | }, | 2880 | }, |
| 2719 | } | 2881 | } |
| 2720 | } | 2882 | switch (b.pending.tail) { |
| 2721 | | 2883 | .none => b.pending.head = index, |
| 2722 | const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { | 2884 | else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, |
| 2723 | error.Unexpected => deadline: { | | |
| 2724 | recoverableOsBugDetected(); | | |
| 2725 | break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; | | |
| 2726 | }, | | |
| 2727 | error.UnsupportedClock => |e| return e, | | |
| 2728 | }; | | |
| 2729 | | | |
| 2730 | while (true) { | | |
| 2731 | var any_pending = false; | | |
| 2732 | for (metadatas, 0..) |*metadata, op_usize| { | | |
| 2733 | if (!metadata.pending) continue; | | |
| 2734 | any_pending = true; | | |
| 2735 | const op: u31 = @intCast(op_usize); | | |
| 2736 | const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; | | |
| 2737 | switch (operations[op]) { | | |
| 2738 | .noop => unreachable, | | |
| 2739 | .file_read_streaming => |*o| { | | |
| 2740 | assert(o.status.pending == b); | | |
| 2741 | if (!done) continue; | | |
| 2742 | o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; | | |
| 2743 | }, | | |
| 2744 | } | | |
| 2745 | metadata.pending = false; | | |
| 2746 | submitComplete(ring, &complete_tail, op); | | |
| 2747 | } | | |
| 2748 | if (b.user.complete_head != complete_tail) return; | | |
| 2749 | if (!any_pending) return; | | |
| 2750 | var delay_interval: windows.LARGE_INTEGER = interval: { | | |
| 2751 | const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); | | |
| 2752 | break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { | | |
| 2753 | error.UnsupportedClock => |e| return e, | | |
| 2754 | error.Unexpected => { | | |
| 2755 | recoverableOsBugDetected(); | | |
| 2756 | break :interval -1; | | |
| 2757 | }, | | |
| 2758 | }; | | |
| 2759 | }; | | |
| 2760 | const alertable_syscall = try AlertableSyscall.start(); | | |
| 2761 | const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); | | |
| 2762 | alertable_syscall.finish(); | | |
| 2763 | switch (delay_rc) { | | |
| 2764 | .SUCCESS, .TIMEOUT => { | | |
| 2765 | // The thread woke due to the timeout. Although spurious | | |
| 2766 | // timeouts are OK, when no deadline is passed we must not | | |
| 2767 | // return `error.Timeout`. | | |
| 2768 | if (timeout != .none) return error.Timeout; | | |
| 2769 | }, | | |
| 2770 | else => {}, | | |
| 2771 | } | 2885 | } |
| | 2886 | b.pending.tail = index; |
| | 2887 | index = submission.node.next; |
| 2772 | } | 2888 | } |
| | 2889 | b.submissions = .{ .head = .none, .tail = .none }; |
| 2773 | } | 2890 | } |
| 2774 | | 2891 | |
| 2775 | fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { | 2892 | fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { |
| ... | @@ -8701,7 +8818,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingEr | ... | @@ -8701,7 +8818,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingEr |
| 8701 | .u = .{ .Status = .PENDING }, | 8818 | .u = .{ .Status = .PENDING }, |
| 8702 | .Information = 0, | 8819 | .Information = 0, |
| 8703 | }; | 8820 | }; |
| 8704 | try ntReadFile(file.handle, data, &io_status_block); | 8821 | try ntReadFile(file.handle, data, &noopApc, null, &io_status_block); |
| 8705 | while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { | 8822 | while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { |
| 8706 | // Once we get here we must not return from the function until the | 8823 | // Once we get here we must not return from the function until the |
| 8707 | // operation completes, thereby releasing reference to io_status_block. | 8824 | // operation completes, thereby releasing reference to io_status_block. |
| ... | @@ -8736,12 +8853,20 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { | ... | @@ -8736,12 +8853,20 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { |
| 8736 | } | 8853 | } |
| 8737 | } | 8854 | } |
| 8738 | | 8855 | |
| 8739 | fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!void { | 8856 | fn ntReadFile( |
| | 8857 | handle: windows.HANDLE, |
| | 8858 | data: []const []u8, |
| | 8859 | apcRoutine: ?*const windows.IO_APC_ROUTINE, |
| | 8860 | apc_context: ?*anyopaque, |
| | 8861 | iosb: *windows.IO_STATUS_BLOCK, |
| | 8862 | ) Io.Cancelable!void { |
| 8740 | var index: usize = 0; | 8863 | var index: usize = 0; |
| 8741 | while (index < data.len and data[index].len == 0) index += 1; | 8864 | while (index < data.len and data[index].len == 0) index += 1; |
| 8742 | if (index == data.len) { | 8865 | if (index == data.len) { |
| 8743 | iosb.u.Status = .SUCCESS; | 8866 | iosb.* = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; |
| 8744 | iosb.Information = 0; | 8867 | if (apcRoutine) |routine| if (routine != &noopApc) { |
| | 8868 | _ = windows.ntdll.NtQueueApcThread(windows.current_process, routine, apc_context, iosb, null); |
| | 8869 | }; |
| 8745 | return; | 8870 | return; |
| 8746 | } | 8871 | } |
| 8747 | const buffer = data[index]; | 8872 | const buffer = data[index]; |
| ... | @@ -8750,8 +8875,8 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT | ... | @@ -8750,8 +8875,8 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT |
| 8750 | while (true) switch (windows.ntdll.NtReadFile( | 8875 | while (true) switch (windows.ntdll.NtReadFile( |
| 8751 | handle, | 8876 | handle, |
| 8752 | null, // event | 8877 | null, // event |
| 8753 | noopApc, // apc callback | 8878 | apcRoutine, |
| 8754 | null, // apc context | 8879 | apc_context, |
| 8755 | iosb, | 8880 | iosb, |
| 8756 | buffer.ptr, | 8881 | buffer.ptr, |
| 8757 | @min(std.math.maxInt(u32), buffer.len), | 8882 | @min(std.math.maxInt(u32), buffer.len), |