| ... | @@ -607,6 +607,34 @@ pub const IO_Uring = struct { | ... | @@ -607,6 +607,34 @@ pub const IO_Uring = struct { |
| 607 | return sqe; | 607 | return sqe; |
| 608 | } | 608 | } |
| 609 | | 609 | |
| | 610 | /// Queues (but does not submit) an SQE to add a link timeout operation. |
| | 611 | /// Returns a pointer to the SQE. |
| | 612 | /// |
| | 613 | /// You need to set linux.IOSQE_IO_LINK to flags of the target operation |
| | 614 | /// and then call this method right after the target operation. |
| | 615 | /// See https://lwn.net/Articles/803932/ for detail. |
| | 616 | /// |
| | 617 | /// If the dependent request finishes before the linked timeout, the timeout |
| | 618 | /// is canceled. If the timeout finishes before the dependent request, the |
| | 619 | /// dependent request will be canceled. |
| | 620 | /// |
| | 621 | /// The completion event result of the link_timeout will be |
| | 622 | /// `-ETIME` if the timeout finishes before the dependent request |
| | 623 | /// (in this case, the completion event result of the dependent request will |
| | 624 | /// be `-ECANCELED`), or |
| | 625 | /// `-EALREADY` if the dependent request finishes before the linked timeout. |
| | 626 | pub fn link_timeout( |
| | 627 | self: *IO_Uring, |
| | 628 | user_data: u64, |
| | 629 | ts: *const os.linux.kernel_timespec, |
| | 630 | flags: u32, |
| | 631 | ) !*io_uring_sqe { |
| | 632 | const sqe = try self.get_sqe(); |
| | 633 | io_uring_prep_link_timeout(sqe, ts, flags); |
| | 634 | sqe.user_data = user_data; |
| | 635 | return sqe; |
| | 636 | } |
| | 637 | |
| 610 | /// Queues (but does not submit) an SQE to perform a `poll(2)`. | 638 | /// Queues (but does not submit) an SQE to perform a `poll(2)`. |
| 611 | /// Returns a pointer to the SQE. | 639 | /// Returns a pointer to the SQE. |
| 612 | pub fn poll_add( | 640 | pub fn poll_add( |
| ... | @@ -1170,6 +1198,15 @@ pub fn io_uring_prep_timeout_remove(sqe: *io_uring_sqe, timeout_user_data: u64, | ... | @@ -1170,6 +1198,15 @@ pub fn io_uring_prep_timeout_remove(sqe: *io_uring_sqe, timeout_user_data: u64, |
| 1170 | }; | 1198 | }; |
| 1171 | } | 1199 | } |
| 1172 | | 1200 | |
| | 1201 | pub fn io_uring_prep_link_timeout( |
| | 1202 | sqe: *io_uring_sqe, |
| | 1203 | ts: *const os.linux.kernel_timespec, |
| | 1204 | flags: u32, |
| | 1205 | ) void { |
| | 1206 | linux.io_uring_prep_rw(.LINK_TIMEOUT, sqe, -1, @ptrToInt(ts), 1, 0); |
| | 1207 | sqe.rw_flags = flags; |
| | 1208 | } |
| | 1209 | |
| 1173 | pub fn io_uring_prep_poll_add( | 1210 | pub fn io_uring_prep_poll_add( |
| 1174 | sqe: *io_uring_sqe, | 1211 | sqe: *io_uring_sqe, |
| 1175 | fd: os.fd_t, | 1212 | fd: os.fd_t, |
| ... | @@ -1803,6 +1840,69 @@ test "timeout_remove" { | ... | @@ -1803,6 +1840,69 @@ test "timeout_remove" { |
| 1803 | }, cqe_timeout_remove); | 1840 | }, cqe_timeout_remove); |
| 1804 | } | 1841 | } |
| 1805 | | 1842 | |
| | 1843 | test "timeout_link_chain1" { |
| | 1844 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 1845 | |
| | 1846 | var ring = IO_Uring.init(8, 0) catch |err| switch (err) { |
| | 1847 | error.SystemOutdated => return error.SkipZigTest, |
| | 1848 | error.PermissionDenied => return error.SkipZigTest, |
| | 1849 | else => return err, |
| | 1850 | }; |
| | 1851 | defer ring.deinit(); |
| | 1852 | |
| | 1853 | var fds = try os.pipe(); |
| | 1854 | defer { |
| | 1855 | os.close(fds[0]); |
| | 1856 | os.close(fds[1]); |
| | 1857 | } |
| | 1858 | |
| | 1859 | var buffer = [_]u8{0} ** 128; |
| | 1860 | const iovecs = [_]os.iovec{os.iovec{ .iov_base = &buffer, .iov_len = buffer.len }}; |
| | 1861 | const sqe_readv = try ring.readv(0x11111111, fds[0], &iovecs, 0); |
| | 1862 | sqe_readv.flags |= linux.IOSQE_IO_LINK; |
| | 1863 | |
| | 1864 | const ts = os.linux.kernel_timespec{ .tv_sec = 0, .tv_nsec = 1000000 }; |
| | 1865 | const seq_link_timeout = try ring.link_timeout(0x22222222, &ts, 0); |
| | 1866 | seq_link_timeout.flags |= linux.IOSQE_IO_LINK; |
| | 1867 | |
| | 1868 | _ = try ring.nop(0x33333333); |
| | 1869 | |
| | 1870 | const nr_wait = try ring.submit(); |
| | 1871 | try testing.expectEqual(@as(u32, 3), nr_wait); |
| | 1872 | |
| | 1873 | var i: usize = 0; |
| | 1874 | while (i < nr_wait) : (i += 1) { |
| | 1875 | const cqe = try ring.copy_cqe(); |
| | 1876 | switch (cqe.user_data) { |
| | 1877 | // poll cancel really should return -ECANCEL... |
| | 1878 | 0x11111111 => { |
| | 1879 | if (cqe.res != -@as(i32, @enumToInt(linux.E.INTR)) and |
| | 1880 | cqe.res != -@as(i32, @enumToInt(linux.E.CANCELED))) |
| | 1881 | { |
| | 1882 | std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); |
| | 1883 | try testing.expect(false); |
| | 1884 | } |
| | 1885 | }, |
| | 1886 | 0x22222222 => { |
| | 1887 | // FASTPOLL kernels can cancel successfully |
| | 1888 | if (cqe.res != -@as(i32, @enumToInt(linux.E.ALREADY)) and |
| | 1889 | cqe.res != -@as(i32, @enumToInt(linux.E.TIME))) |
| | 1890 | { |
| | 1891 | std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); |
| | 1892 | try testing.expect(false); |
| | 1893 | } |
| | 1894 | }, |
| | 1895 | 0x33333333 => { |
| | 1896 | if (cqe.res != -@as(i32, @enumToInt(linux.E.CANCELED))) { |
| | 1897 | std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res }); |
| | 1898 | try testing.expect(false); |
| | 1899 | } |
| | 1900 | }, |
| | 1901 | else => @panic("should not happen"), |
| | 1902 | } |
| | 1903 | } |
| | 1904 | } |
| | 1905 | |
| 1806 | test "fallocate" { | 1906 | test "fallocate" { |
| 1807 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 1907 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 1808 | | 1908 | |