| ... | ... | @@ -1617,7 +1617,8 @@ pub fn io(t: *Threaded) Io { |
| 1617 | 1617 | .futexWake = futexWake, |
| 1618 | 1618 | |
| 1619 | 1619 | .operate = operate, |
| 1620 | | .batchWait = batchWait, |
| 1620 | .batchAwaitAsync = batchAwaitAsync, |
| 1621 | .batchAwaitConcurrent = batchAwaitConcurrent, |
| 1621 | 1622 | .batchCancel = batchCancel, |
| 1622 | 1623 | |
| 1623 | 1624 | .dirCreateDir = dirCreateDir, |
| ... | ... | @@ -1780,7 +1781,8 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1780 | 1781 | .futexWake = futexWake, |
| 1781 | 1782 | |
| 1782 | 1783 | .operate = operate, |
| 1783 | | .batchWait = batchWait, |
| 1784 | .batchAwaitAsync = batchAwaitAsync, |
| 1785 | .batchAwaitConcurrent = batchAwaitConcurrent, |
| 1784 | 1786 | .batchCancel = batchCancel, |
| 1785 | 1787 | |
| 1786 | 1788 | .dirCreateDir = dirCreateDir, |
| ... | ... | @@ -2483,85 +2485,227 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2483 | 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 | 2489 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2488 | | switch (op.*) { |
| 2489 | | .noop => |*o| { |
| 2490 | | _ = o.status.unstarted; |
| 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) { |
| 2490 | switch (operation) { |
| 2491 | .file_read_streaming => |o| return .{ |
| 2492 | .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { |
| 2496 | 2493 | error.Canceled => |e| return e, |
| 2497 | 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 | 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 | 2509 | if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); |
| 2507 | | const operations = b.operations; |
| 2508 | | const len: u31 = @intCast(operations.len); |
| 2509 | | const ring = b.ring[0..len]; |
| 2510 | | var submit_head = b.impl.submit_head; |
| 2511 | | const submit_tail = b.user.submit_tail; |
| 2512 | | b.impl.submit_tail = submit_tail; |
| 2513 | | var complete_tail = b.impl.complete_tail; |
| 2514 | | var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index |
| 2515 | | var poll_i: u8 = 0; |
| 2516 | | defer { |
| 2517 | | for (map_buffer[0..poll_i]) |op| { |
| 2518 | | submit_head = submit_head.prev(len); |
| 2519 | | ring[submit_head.index(len)] = op; |
| 2520 | | } |
| 2521 | | b.impl.submit_head = submit_head; |
| 2522 | | b.impl.complete_tail = complete_tail; |
| 2523 | | b.user.complete_tail = complete_tail; |
| 2510 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2511 | var poll_len: u32 = 0; |
| 2512 | { |
| 2513 | var index = b.submissions.head; |
| 2514 | while (index != .none and poll_len < poll_buffer_len) { |
| 2515 | const submission = &b.storage[index.toIndex()].submission; |
| 2516 | switch (submission.operation) { |
| 2517 | .file_read_streaming => |o| { |
| 2518 | poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; |
| 2519 | poll_len += 1; |
| 2520 | }, |
| 2521 | } |
| 2522 | index = submission.node.next; |
| 2523 | } |
| 2524 | } |
| 2525 | switch (poll_len) { |
| 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 | 2647 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2526 | | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { |
| 2527 | | const op = ring[submit_head.index(len)]; |
| 2528 | | const operation = &operations[op]; |
| 2529 | | switch (operation.*) { |
| 2530 | | .noop => |*o| { |
| 2531 | | _ = o.status.unstarted; |
| 2532 | | o.status = .{ .result = {} }; |
| 2533 | | submitComplete(ring, &complete_tail, op); |
| 2534 | | }, |
| 2535 | | .file_read_streaming => |*o| { |
| 2536 | | _ = o.status.unstarted; |
| 2537 | | if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; |
| 2538 | | poll_buffer[poll_i] = .{ |
| 2539 | | .fd = o.file.handle, |
| 2540 | | .events = posix.POLL.IN, |
| 2541 | | .revents = 0, |
| 2648 | var poll_storage: struct { |
| 2649 | gpa: std.mem.Allocator, |
| 2650 | b: *Io.Batch, |
| 2651 | slice: []posix.pollfd, |
| 2652 | len: u32, |
| 2653 | |
| 2654 | fn add(storage: *@This(), file: Io.File, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void { |
| 2655 | const len = storage.len; |
| 2656 | if (len == poll_buffer_len) { |
| 2657 | const slice: []posix.pollfd = if (storage.b.context) |context| |
| 2658 | @as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..storage.b.storage.len] |
| 2659 | else allocation: { |
| 2660 | const allocation = storage.gpa.alloc(posix.pollfd, storage.b.storage.len) catch |
| 2661 | return error.ConcurrencyUnavailable; |
| 2662 | storage.b.context = allocation.ptr; |
| 2663 | break :allocation allocation; |
| 2542 | 2664 | }; |
| 2543 | | map_buffer[poll_i] = @intCast(op); |
| 2544 | | poll_i += 1; |
| 2545 | | }, |
| 2665 | @memcpy(slice[0..poll_buffer_len], storage.slice); |
| 2666 | } |
| 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 | 2686 | 0 => return, |
| 2550 | 2687 | 1 => if (timeout == .none) { |
| 2551 | | const op = map_buffer[0]; |
| 2552 | | try operate(t, &operations[op]); |
| 2553 | | submitComplete(ring, &complete_tail, op); |
| 2554 | | poll_i = 0; |
| 2688 | const index = b.submissions.head; |
| 2689 | const storage = &b.storage[index.toIndex()]; |
| 2690 | const result = try operate(t, storage.submission.operation); |
| 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 | 2700 | return; |
| 2556 | 2701 | }, |
| 2557 | 2702 | else => {}, |
| 2558 | 2703 | } |
| 2559 | 2704 | const t_io = ioBasic(t); |
| 2560 | 2705 | const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; |
| 2561 | | const max_poll_ms = std.math.maxInt(i32); |
| 2562 | 2706 | while (true) { |
| 2563 | 2707 | const timeout_ms: i32 = t: { |
| 2564 | | if (b.user.complete_head != complete_tail) { |
| 2708 | if (b.completions.head != .none) { |
| 2565 | 2709 | // It is legal to call batchWait with already completed |
| 2566 | 2710 | // operations in the ring. In such case, we need to avoid |
| 2567 | 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 | 2715 | const d = deadline orelse break :t -1; |
| 2572 | 2716 | const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; |
| 2573 | 2717 | if (duration.raw.nanoseconds <= 0) return error.Timeout; |
| 2718 | const max_poll_ms = std.math.maxInt(i32); |
| 2574 | 2719 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); |
| 2575 | 2720 | }; |
| 2576 | 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 | 2723 | syscall.finish(); |
| 2579 | 2724 | switch (posix.errno(rc)) { |
| 2580 | 2725 | .SUCCESS => { |
| 2581 | 2726 | if (rc == 0) { |
| 2582 | | if (b.user.complete_head != complete_tail) { |
| 2727 | if (b.completions.head != .none) { |
| 2583 | 2728 | // Since there are already completions available in the |
| 2584 | 2729 | // queue, this is neither a timeout nor a case for |
| 2585 | 2730 | // retrying. |
| ... | ... | @@ -2590,18 +2735,30 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. |
| 2590 | 2735 | if (deadline == null) continue; |
| 2591 | 2736 | return error.Timeout; |
| 2592 | 2737 | } |
| 2593 | | while (poll_i != 0) { |
| 2594 | | poll_i -= 1; |
| 2595 | | const poll_fd = &poll_buffer[poll_i]; |
| 2596 | | const op = map_buffer[poll_i]; |
| 2597 | | if (poll_fd.revents == 0) { |
| 2598 | | submit_head = submit_head.prev(len); |
| 2599 | | ring[submit_head.index(len)] = op; |
| 2600 | | } else { |
| 2601 | | try operate(t, &operations[op]); |
| 2602 | | submitComplete(ring, &complete_tail, op); |
| 2603 | | } |
| 2738 | var prev_index: Io.Operation.OptionalIndex = .none; |
| 2739 | var index = b.submissions.head; |
| 2740 | for (poll_storage.slice[0..poll_storage.len]) |poll_entry| { |
| 2741 | const submission = &b.storage[index.toIndex()].submission; |
| 2742 | const next_index = submission.node.next; |
| 2743 | if (poll_entry.revents != 0) { |
| 2744 | const result = try operate(t, submission.operation); |
| 2745 | |
| 2746 | switch (prev_index) { |
| 2747 | .none => b.submissions.head = next_index, |
| 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 | 2762 | return; |
| 2606 | 2763 | }, |
| 2607 | 2764 | .INTR => continue, |
| ... | ... | @@ -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 | 2789 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2614 | 2790 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2615 | | const operations = b.operations; |
| 2616 | | const len: u31 = @intCast(operations.len); |
| 2617 | | const ring = b.ring[0..len]; |
| 2618 | | var submit_head = b.impl.submit_head; |
| 2619 | | const submit_tail = b.user.submit_tail; |
| 2620 | | b.impl.submit_tail = submit_tail; |
| 2621 | | var complete_tail = b.impl.complete_tail; |
| 2622 | | while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { |
| 2623 | | const op = ring[submit_head.index(len)]; |
| 2624 | | switch (operations[op]) { |
| 2625 | | .noop => |*o| { |
| 2626 | | _ = o.status.unstarted; |
| 2627 | | o.status = .{ .result = {} }; |
| 2628 | | submitComplete(ring, &complete_tail, op); |
| 2629 | | }, |
| 2630 | | .file_read_streaming => |*o| _ = o.status.unstarted, |
| 2791 | { |
| 2792 | var tail_index = b.unused.tail; |
| 2793 | defer b.unused.tail = tail_index; |
| 2794 | var index = b.submissions.head; |
| 2795 | errdefer b.submissions.head = index; |
| 2796 | while (index != .none) { |
| 2797 | const next_index = b.storage[index.toIndex()].submission.node.next; |
| 2798 | switch (tail_index) { |
| 2799 | .none => b.unused.head = index, |
| 2800 | else => b.storage[tail_index.toIndex()].unused.next = index, |
| 2801 | } |
| 2802 | b.storage[index.toIndex()] = .{ .unused = .{ .prev = tail_index, .next = .none } }; |
| 2803 | tail_index = index; |
| 2804 | index = next_index; |
| 2631 | 2805 | } |
| 2806 | b.submissions = .{ .head = .none, .tail = .none }; |
| 2632 | 2807 | } |
| 2633 | 2808 | if (is_windows) { |
| 2634 | | // Iterate over pending and issue cancelations, then free the allocation for IO_STATUS_BLOCK |
| 2635 | | if (b.impl.reserved) |reserved| { |
| 2636 | | const gpa = t.allocator; |
| 2637 | | const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved)); |
| 2638 | | const metadatas = metadatas_ptr[0..b.operations.len]; |
| 2639 | | for (metadatas, 0..) |*metadata, op| { |
| 2640 | | if (!metadata.pending) continue; |
| 2641 | | const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; |
| 2642 | | if (done) continue; |
| 2643 | | switch (operations[op]) { |
| 2644 | | .noop => unreachable, |
| 2645 | | .file_read_streaming => |*o| { |
| 2646 | | _ = windows.ntdll.NtCancelIoFile(o.file.handle, &metadata.iosb); |
| 2647 | | }, |
| 2648 | | } |
| 2809 | var index = b.pending.head; |
| 2810 | while (index != .none) { |
| 2811 | const pending = &b.storage[index.toIndex()].pending; |
| 2812 | const context: *WindowsBatchPendingOperationContext = .fromErased(&pending.context); |
| 2813 | _ = windows.ntdll.NtCancelIoFile(context.file, &context.iosb); |
| 2814 | index = pending.node.next; |
| 2815 | } |
| 2816 | while (b.pending.head != .none) waitForApcOrAlert(); |
| 2817 | } else if (b.context) |context| { |
| 2818 | t.allocator.free(@as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..b.storage.len]); |
| 2819 | b.context = null; |
| 2820 | } |
| 2821 | assert(b.pending.head == .none); |
| 2822 | } |
| 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| { |
| 2651 | | if (!metadata.pending) continue; |
| 2652 | | while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { |
| 2653 | | waitForApcOrAlert(); |
| 2654 | | } |
| 2846 | storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; |
| 2847 | b.unused.tail = .fromIndex(index); |
| 2848 | }, |
| 2849 | else => { |
| 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); |
| 2657 | | b.impl.reserved = null; |
| 2658 | | } |
| 2854 | b.completions.tail = .fromIndex(index); |
| 2855 | const result: Io.Operation.Result = switch (pending.tag) { |
| 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 { |
| 2666 | | iosb: windows.IO_STATUS_BLOCK, |
| 2667 | | pending: bool, |
| 2668 | | }; |
| 2669 | | |
| 2670 | | fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { |
| 2671 | | const operations = b.operations; |
| 2672 | | const len: u31 = @intCast(operations.len); |
| 2673 | | const ring = b.ring[0..len]; |
| 2674 | | var submit_head = b.impl.submit_head; |
| 2675 | | const submit_tail = b.user.submit_tail; |
| 2676 | | b.impl.submit_tail = submit_tail; |
| 2677 | | var complete_tail = b.impl.complete_tail; |
| 2678 | | |
| 2679 | | const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: { |
| 2680 | | const gpa = t.allocator; |
| 2681 | | const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable; |
| 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 | | } |
| 2863 | fn batchAwaitWindows(b: *Io.Batch) Io.Cancelable!void { |
| 2864 | var index = b.submissions.head; |
| 2865 | errdefer b.submissions.head = index; |
| 2866 | while (index != .none) { |
| 2867 | const storage = &b.storage[index.toIndex()]; |
| 2868 | const submission = storage.submission; |
| 2869 | errdefer storage.* = .{ .submission = submission }; |
| 2870 | storage.* = .{ .pending = .{ |
| 2871 | .node = .{ .prev = b.pending.tail, .next = .none }, |
| 2872 | .tag = submission.operation, |
| 2873 | .context = undefined, |
| 2874 | } }; |
| 2875 | const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context); |
| 2876 | switch (submission.operation) { |
| 2877 | .file_read_streaming => |o| { |
| 2878 | context.file = o.file.handle; |
| 2879 | try ntReadFile(o.file.handle, o.data, &batchApc, b, &context.iosb); |
| 2718 | 2880 | }, |
| 2719 | 2881 | } |
| 2720 | | } |
| 2721 | | |
| 2722 | | const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { |
| 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 => {}, |
| 2882 | switch (b.pending.tail) { |
| 2883 | .none => b.pending.head = index, |
| 2884 | else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, |
| 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 | 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 | 8818 | .u = .{ .Status = .PENDING }, |
| 8702 | 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 | 8822 | while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { |
| 8706 | 8823 | // Once we get here we must not return from the function until the |
| 8707 | 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 | 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 | 8863 | var index: usize = 0; |
| 8741 | 8864 | while (index < data.len and data[index].len == 0) index += 1; |
| 8742 | 8865 | if (index == data.len) { |
| 8743 | | iosb.u.Status = .SUCCESS; |
| 8744 | | iosb.Information = 0; |
| 8866 | iosb.* = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; |
| 8867 | if (apcRoutine) |routine| if (routine != &noopApc) { |
| 8868 | _ = windows.ntdll.NtQueueApcThread(windows.current_process, routine, apc_context, iosb, null); |
| 8869 | }; |
| 8745 | 8870 | return; |
| 8746 | 8871 | } |
| 8747 | 8872 | const buffer = data[index]; |
| ... | ... | @@ -8750,8 +8875,8 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT |
| 8750 | 8875 | while (true) switch (windows.ntdll.NtReadFile( |
| 8751 | 8876 | handle, |
| 8752 | 8877 | null, // event |
| 8753 | | noopApc, // apc callback |
| 8754 | | null, // apc context |
| 8878 | apcRoutine, |
| 8879 | apc_context, |
| 8755 | 8880 | iosb, |
| 8756 | 8881 | buffer.ptr, |
| 8757 | 8882 | @min(std.math.maxInt(u32), buffer.len), |