| ... | ... | @@ -441,22 +441,75 @@ pub const IO_Uring = struct { |
| 441 | 441 | sqe.user_data = user_data; |
| 442 | 442 | return sqe; |
| 443 | 443 | } |
| 444 | | |
| 445 | | /// Like `link_with_next_sqe()` but stronger. |
| 446 | | /// For when you don't want the chain to fail in the event of a completion result error. |
| 447 | | /// For example, you may know that some commands will fail and may want the chain to continue. |
| 448 | | /// Hard links are resilient to completion results, but are not resilient to submission errors. |
| 449 | | pub fn hardlink_with_next_sqe(self: *IO_Uring, sqe: *io_uring_sqe) void { |
| 450 | | sqe.flags |= linux.IOSQE_IO_HARDLINK; |
| 444 | |
| 445 | /// Queue (but does not submit) an SQE to perform a `connect(2)` on a socket. |
| 446 | /// Returns a pointer to the SQE. |
| 447 | pub fn connect( |
| 448 | self: *IO_Uring, |
| 449 | user_data: u64, |
| 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 | | |
| 453 | | /// This creates a full pipeline barrier in the submission queue. |
| 454 | | /// This SQE will not be started until previous SQEs complete. |
| 455 | | /// Subsequent SQEs will not be started until this SQE completes. |
| 456 | | /// In other words, this stalls the entire submission queue. |
| 457 | | /// You should first consider using link_with_next_sqe() for more granular SQE sequence control. |
| 458 | | pub fn drain_previous_sqes(self: *IO_Uring, sqe: *io_uring_sqe) void { |
| 459 | | sqe.flags |= linux.IOSQE_IO_DRAIN; |
| 459 | |
| 460 | /// Queues (but does not submit) an SQE to perform a `recv(2)`. |
| 461 | /// Returns a pointer to the SQE. |
| 462 | pub fn recv( |
| 463 | self: *IO_Uring, |
| 464 | user_data: u64, |
| 465 | fd: os.fd_t, |
| 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 | 515 | /// Registers an array of file descriptors. |
| ... | ... | @@ -1007,3 +1060,133 @@ test "write/read" { |
| 1007 | 1060 | }, cqe_read); |
| 1008 | 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 | } |