| ... | @@ -441,22 +441,75 @@ pub const IO_Uring = struct { | ... | @@ -441,22 +441,75 @@ pub const IO_Uring = struct { |
| 441 | sqe.user_data = user_data; | 441 | sqe.user_data = user_data; |
| 442 | return sqe; | 442 | return sqe; |
| 443 | } | 443 | } |
| 444 | | 444 | |
| 445 | /// Like `link_with_next_sqe()` but stronger. | 445 | /// Queue (but does not submit) an SQE to perform a `connect(2)` on a socket. |
| 446 | /// For when you don't want the chain to fail in the event of a completion result error. | 446 | /// Returns a pointer to the SQE. |
| 447 | /// For example, you may know that some commands will fail and may want the chain to continue. | 447 | pub fn connect( |
| 448 | /// Hard links are resilient to completion results, but are not resilient to submission errors. | 448 | self: *IO_Uring, |
| 449 | pub fn hardlink_with_next_sqe(self: *IO_Uring, sqe: *io_uring_sqe) void { | 449 | user_data: u64, |
| 450 | sqe.flags |= linux.IOSQE_IO_HARDLINK; | 450 | fd: os.fd_t, |
| | 451 | addr: *const os.sockaddr, |
| | 452 | addrlen: os.socklen_t |
| | 453 | ) !*io_uring_sqe { |
| | 454 | const sqe = try self.get_sqe(); |
| | 455 | io_uring_prep_connect(sqe, fd, addr, addrlen); |
| | 456 | sqe.user_data = user_data; |
| | 457 | return sqe; |
| 451 | } | 458 | } |
| 452 | | 459 | |
| 453 | /// This creates a full pipeline barrier in the submission queue. | 460 | /// Queues (but does not submit) an SQE to perform a `recv(2)`. |
| 454 | /// This SQE will not be started until previous SQEs complete. | 461 | /// Returns a pointer to the SQE. |
| 455 | /// Subsequent SQEs will not be started until this SQE completes. | 462 | pub fn recv( |
| 456 | /// In other words, this stalls the entire submission queue. | 463 | self: *IO_Uring, |
| 457 | /// You should first consider using link_with_next_sqe() for more granular SQE sequence control. | 464 | user_data: u64, |
| 458 | pub fn drain_previous_sqes(self: *IO_Uring, sqe: *io_uring_sqe) void { | 465 | fd: os.fd_t, |
| 459 | sqe.flags |= linux.IOSQE_IO_DRAIN; | 466 | buffer: []u8, |
| | 467 | flags: u32 |
| | 468 | ) !*io_uring_sqe { |
| | 469 | const sqe = try self.get_sqe(); |
| | 470 | io_uring_prep_recv(sqe, fd, buffer, flags); |
| | 471 | sqe.user_data = user_data; |
| | 472 | return sqe; |
| | 473 | } |
| | 474 | |
| | 475 | /// Queues (but does not submit) an SQE to perform a `send(2)`. |
| | 476 | /// Returns a pointer to the SQE. |
| | 477 | pub fn send( |
| | 478 | self: *IO_Uring, |
| | 479 | user_data: u64, |
| | 480 | fd: os.fd_t, |
| | 481 | buffer: []u8, |
| | 482 | flags: u32 |
| | 483 | ) !*io_uring_sqe { |
| | 484 | const sqe = try self.get_sqe(); |
| | 485 | io_uring_prep_send(sqe, fd, buffer, flags); |
| | 486 | sqe.user_data = user_data; |
| | 487 | return sqe; |
| | 488 | } |
| | 489 | |
| | 490 | /// Queues (but does not submit) an SQE to perform an `openat(2)`. |
| | 491 | /// Returns a pointer to the SQE. |
| | 492 | pub fn openat( |
| | 493 | self: *IO_Uring, |
| | 494 | user_data: u64, |
| | 495 | fd: os.fd_t, |
| | 496 | path: [*:0]const u8, |
| | 497 | flags: u32, |
| | 498 | mode: os.mode_t |
| | 499 | ) !*io_uring_sqe { |
| | 500 | const sqe = try self.get_sqe(); |
| | 501 | io_uring_prep_openat(sqe, fd, path, flags, mode); |
| | 502 | sqe.user_data = user_data; |
| | 503 | return sqe; |
| | 504 | } |
| | 505 | |
| | 506 | /// Queues (but does not submit) an SQE to perform a `close(2)`. |
| | 507 | /// Returns a pointer to the SQE. |
| | 508 | pub fn close(self: *IO_Uring, user_data: u64, fd: os.fd_t) !*io_uring_sqe { |
| | 509 | const sqe = try self.get_sqe(); |
| | 510 | io_uring_prep_close(sqe, fd); |
| | 511 | sqe.user_data = user_data; |
| | 512 | return sqe; |
| 460 | } | 513 | } |
| 461 | | 514 | |
| 462 | /// Registers an array of file descriptors. | 515 | /// Registers an array of file descriptors. |
| ... | @@ -1007,3 +1060,133 @@ test "write/read" { | ... | @@ -1007,3 +1060,133 @@ test "write/read" { |
| 1007 | }, cqe_read); | 1060 | }, cqe_read); |
| 1008 | testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); | 1061 | testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); |
| 1009 | } | 1062 | } |
| | 1063 | |
| | 1064 | test "openat/close" { |
| | 1065 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 1066 | |
| | 1067 | var ring = IO_Uring.init(1, 0) catch |err| switch (err) { |
| | 1068 | error.SystemOutdated => return error.SkipZigTest, |
| | 1069 | error.PermissionDenied => return error.SkipZigTest, |
| | 1070 | else => return err |
| | 1071 | }; |
| | 1072 | defer ring.deinit(); |
| | 1073 | |
| | 1074 | const path = "test_io_uring_openat_close"; |
| | 1075 | defer std.fs.cwd().deleteFile(path) catch {}; |
| | 1076 | |
| | 1077 | const flags: u32 = os.O_CLOEXEC | os.O_RDWR | os.O_CREAT; |
| | 1078 | const mode: os.mode_t = 0o666; |
| | 1079 | var sqe_openat = try ring.openat(789, linux.AT_FDCWD, path, flags, mode); |
| | 1080 | testing.expectEqual(io_uring_sqe { |
| | 1081 | .opcode = .OPENAT, |
| | 1082 | .flags = 0, |
| | 1083 | .ioprio = 0, |
| | 1084 | .fd = linux.AT_FDCWD, |
| | 1085 | .off = 0, |
| | 1086 | .addr = @ptrToInt(path), |
| | 1087 | .len = mode, |
| | 1088 | .rw_flags = flags, |
| | 1089 | .user_data = 789, |
| | 1090 | .buf_index = 0, |
| | 1091 | .personality = 0, |
| | 1092 | .splice_fd_in = 0, |
| | 1093 | .__pad2 = [2]u64{ 0, 0 } |
| | 1094 | }, sqe_openat.*); |
| | 1095 | testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 1096 | |
| | 1097 | var cqe_openat = try ring.copy_cqe(); |
| | 1098 | if (cqe_openat.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1099 | testing.expectEqual(@as(u64, 789), cqe_openat.user_data); |
| | 1100 | testing.expect(cqe_openat.res > 0); |
| | 1101 | testing.expectEqual(@as(u32, 0), cqe_openat.flags); |
| | 1102 | |
| | 1103 | var sqe_close = try ring.close(1011, cqe_openat.res); |
| | 1104 | testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode); |
| | 1105 | testing.expectEqual(cqe_openat.res, sqe_close.fd); |
| | 1106 | testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 1107 | |
| | 1108 | var cqe_close = try ring.copy_cqe(); |
| | 1109 | if (cqe_close.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1110 | testing.expectEqual(linux.io_uring_cqe { |
| | 1111 | .user_data = 1011, |
| | 1112 | .res = 0, |
| | 1113 | .flags = 0, |
| | 1114 | }, cqe_close); |
| | 1115 | } |
| | 1116 | |
| | 1117 | test "accept/connect/send/recv" { |
| | 1118 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 1119 | |
| | 1120 | var ring = IO_Uring.init(16, 0) catch |err| switch (err) { |
| | 1121 | error.SystemOutdated => return error.SkipZigTest, |
| | 1122 | error.PermissionDenied => return error.SkipZigTest, |
| | 1123 | else => return err |
| | 1124 | }; |
| | 1125 | defer ring.deinit(); |
| | 1126 | |
| | 1127 | var address = try net.Address.parseIp4("127.0.0.1", 3131); |
| | 1128 | const kernel_backlog = 1; |
| | 1129 | const server = try os.socket(address.any.family, os.SOCK_STREAM | os.SOCK_CLOEXEC, 0); |
| | 1130 | defer os.close(server); |
| | 1131 | try os.setsockopt(server, os.SOL_SOCKET, os.SO_REUSEADDR, &mem.toBytes(@as(c_int, 1))); |
| | 1132 | try os.bind(server, &address.any, address.getOsSockLen()); |
| | 1133 | try os.listen(server, kernel_backlog); |
| | 1134 | |
| | 1135 | var buffer_send = [_]u8{1,0,1,0,1,0,1,0,1,0}; |
| | 1136 | var buffer_recv = [_]u8{0,1,0,1,0}; |
| | 1137 | |
| | 1138 | var accept_addr: os.sockaddr = undefined; |
| | 1139 | var accept_addr_len: os.socklen_t = @sizeOf(@TypeOf(accept_addr)); |
| | 1140 | var accept = try ring.accept(0xaaaaaaaa, server, &accept_addr, &accept_addr_len, 0); |
| | 1141 | testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 1142 | |
| | 1143 | const client = try os.socket(address.any.family, os.SOCK_STREAM | os.SOCK_CLOEXEC, 0); |
| | 1144 | defer os.close(client); |
| | 1145 | var connect = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen()); |
| | 1146 | testing.expectEqual(@as(u32, 1), try ring.submit()); |
| | 1147 | |
| | 1148 | var cqe_accept = try ring.copy_cqe(); |
| | 1149 | if (cqe_accept.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1150 | var cqe_connect = try ring.copy_cqe(); |
| | 1151 | if (cqe_connect.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1152 | |
| | 1153 | // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first: |
| | 1154 | if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) { |
| | 1155 | var a = cqe_accept; |
| | 1156 | var b = cqe_connect; |
| | 1157 | cqe_accept = b; |
| | 1158 | cqe_connect = a; |
| | 1159 | } |
| | 1160 | |
| | 1161 | testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data); |
| | 1162 | testing.expect(cqe_accept.res > 0); |
| | 1163 | testing.expectEqual(@as(u32, 0), cqe_accept.flags); |
| | 1164 | testing.expectEqual(linux.io_uring_cqe { |
| | 1165 | .user_data = 0xcccccccc, |
| | 1166 | .res = 0, |
| | 1167 | .flags = 0, |
| | 1168 | }, cqe_connect); |
| | 1169 | |
| | 1170 | var send = try ring.send(0xeeeeeeee, client, buffer_send[0..], 0); |
| | 1171 | send.flags |= linux.IOSQE_IO_LINK; |
| | 1172 | var recv = try ring.recv(0xffffffff, cqe_accept.res, buffer_recv[0..], 0); |
| | 1173 | testing.expectEqual(@as(u32, 2), try ring.submit()); |
| | 1174 | |
| | 1175 | var cqe_send = try ring.copy_cqe(); |
| | 1176 | if (cqe_send.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1177 | testing.expectEqual(linux.io_uring_cqe { |
| | 1178 | .user_data = 0xeeeeeeee, |
| | 1179 | .res = buffer_send.len, |
| | 1180 | .flags = 0, |
| | 1181 | }, cqe_send); |
| | 1182 | |
| | 1183 | var cqe_recv = try ring.copy_cqe(); |
| | 1184 | if (cqe_recv.res == -linux.EINVAL) return error.SkipZigTest; |
| | 1185 | testing.expectEqual(linux.io_uring_cqe { |
| | 1186 | .user_data = 0xffffffff, |
| | 1187 | .res = buffer_recv.len, |
| | 1188 | .flags = 0, |
| | 1189 | }, cqe_recv); |
| | 1190 | |
| | 1191 | testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); |
| | 1192 | } |