authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-11-17 16:31:57+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-11-17 16:31:57+01:00
log87dd8d56690b4e8187d9a2a9fda9c611199f430f
treeafc3c541cd1c1ff950bb0e893b829e2bfdf6196b
parent256384a2ec2f67fc1b9380be987c63d5702f180c

io_uring: simplify tests by checking kernel version

So far we relied on getting EINVAL in CQE for operations that kernel don't support. The problem with that approach is that there are many other reasons (like wrong params) to get EINVAL. The other problem is when we have an operation that existed before and gets new behavior via different attributes, like accept and accept_direct. Older kernels can fall back to non direct operation although we set attributes for direct operation. Operation completes successfully in both cases but with different results. This commit introduces kernel version check at the start of the test. Making body of the test free of checking for various kernel version differences. Feature availability references: * https://manpages.debian.org/unstable/liburing-dev/io_uring_enter.2.en.html * https://kernel.dk/axboe-kr2022.pdf * https://github.com/Jiboo/zig/blob/5acf7969bc759ce51005924109924d9666f74429/lib/std/os/linux.zig#L3727 * https://github.com/Jiboo/zig/blob/5acf7969bc759ce51005924109924d9666f74429/lib/std/os/linux.zig#L3993

1 files changed, 80 insertions(+), 71 deletions(-)

lib/std/os/linux/io_uring.zig+80-71
......@@ -491,6 +491,7 @@ pub const IO_Uring = struct {
491491
492492 /// Queues (but does not submit) an SQE to perform an `accept4(2)` on a socket.
493493 /// Returns a pointer to the SQE.
494 /// Available since 5.5
494495 pub fn accept(
495496 self: *IO_Uring,
496497 user_data: u64,
......@@ -511,6 +512,8 @@ pub const IO_Uring = struct {
511512 /// which will repeatedly trigger a CQE when a connection request comes in.
512513 /// While IORING_CQE_F_MORE flag is set in CQE flags accept will generate
513514 /// further CQEs.
515 ///
516 /// Available since 5.19
514517 pub fn accept_multishot(
515518 self: *IO_Uring,
516519 user_data: u64,
......@@ -534,6 +537,8 @@ pub const IO_Uring = struct {
534537 /// After creation, they can be used by setting IOSQE_FIXED_FILE in the SQE
535538 /// flags member, and setting the SQE fd field to the direct descriptor value
536539 /// rather than the regular file descriptor.
540 ///
541 /// Available since 5.19
537542 pub fn accept_direct(
538543 self: *IO_Uring,
539544 user_data: u64,
......@@ -549,6 +554,7 @@ pub const IO_Uring = struct {
549554 }
550555
551556 /// Queues an multishot accept using direct (registered) file descriptors.
557 /// Available since 5.19
552558 pub fn accept_multishot_direct(
553559 self: *IO_Uring,
554560 user_data: u64,
......@@ -726,6 +732,7 @@ pub const IO_Uring = struct {
726732
727733 /// Queues (but does not submit) an SQE to perform an `openat(2)`.
728734 /// Returns a pointer to the SQE.
735 /// Available since 5.6.
729736 pub fn openat(
730737 self: *IO_Uring,
731738 user_data: u64,
......@@ -749,6 +756,8 @@ pub const IO_Uring = struct {
749756 /// After creation, they can be used by setting IOSQE_FIXED_FILE in the SQE
750757 /// flags member, and setting the SQE fd field to the direct descriptor value
751758 /// rather than the regular file descriptor.
759 ///
760 /// Available since 5.15
752761 pub fn openat_direct(
753762 self: *IO_Uring,
754763 user_data: u64,
......@@ -766,6 +775,7 @@ pub const IO_Uring = struct {
766775
767776 /// Queues (but does not submit) an SQE to perform a `close(2)`.
768777 /// Returns a pointer to the SQE.
778 /// Available since 5.6.
769779 pub fn close(self: *IO_Uring, user_data: u64, fd: os.fd_t) !*linux.io_uring_sqe {
770780 const sqe = try self.get_sqe();
771781 io_uring_prep_close(sqe, fd);
......@@ -774,6 +784,7 @@ pub const IO_Uring = struct {
774784 }
775785
776786 /// Queues close of registered file descriptor.
787 /// Available since 5.15
777788 pub fn close_direct(self: *IO_Uring, user_data: u64, file_index: u32) !*linux.io_uring_sqe {
778789 const sqe = try self.get_sqe();
779790 io_uring_prep_close_direct(sqe, file_index);
......@@ -1232,6 +1243,7 @@ pub const IO_Uring = struct {
12321243
12331244 /// Prepares a socket creation request.
12341245 /// New socket fd will be returned in completion result.
1246 /// Available since 5.19
12351247 pub fn socket(
12361248 self: *IO_Uring,
12371249 user_data: u64,
......@@ -1247,6 +1259,7 @@ pub const IO_Uring = struct {
12471259 }
12481260
12491261 /// Prepares a socket creation request for registered file at index `file_index`.
1262 /// Available since 5.19
12501263 pub fn socket_direct(
12511264 self: *IO_Uring,
12521265 user_data: u64,
......@@ -1264,6 +1277,7 @@ pub const IO_Uring = struct {
12641277
12651278 /// Prepares a socket creation request for registered file, index chosen by kernel (file index alloc).
12661279 /// File index will be returned in CQE res field.
1280 /// Available since 5.19
12671281 pub fn socket_direct_alloc(
12681282 self: *IO_Uring,
12691283 user_data: u64,
......@@ -3826,22 +3840,15 @@ test "accept/connect/send_zc/recv" {
38263840}
38273841
38283842test "accept_direct" {
3829 if (builtin.os.tag != .linux) return error.SkipZigTest;
3843 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
38303844
3831 var ring = IO_Uring.init(1, 0) catch |err| switch (err) {
3832 error.SystemOutdated => return error.SkipZigTest,
3833 error.PermissionDenied => return error.SkipZigTest,
3834 else => return err,
3835 };
3845 var ring = try IO_Uring.init(1, 0);
38363846 defer ring.deinit();
38373847 var address = try net.Address.parseIp4("127.0.0.1", 0);
38383848
38393849 // register direct file descriptors
38403850 var registered_fds = [_]os.fd_t{-1} ** 2;
3841 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
3842 error.FileDescriptorInvalid => return error.SkipZigTest,
3843 else => return err,
3844 };
3851 try ring.register_files(registered_fds[0..]);
38453852
38463853 const listener_socket = try createListenerSocket(&address);
38473854 defer os.closeSocket(listener_socket);
......@@ -3866,10 +3873,8 @@ test "accept_direct" {
38663873
38673874 // accept completion
38683875 const cqe_accept = try ring.copy_cqe();
3869 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
38703876 try testing.expectEqual(os.E.SUCCESS, cqe_accept.err());
38713877 const fd_index = cqe_accept.res;
3872 if (fd_index >= registered_fds.len) return error.SkipZigTest; // old kernel fallback to ordinary accept
38733878 try testing.expect(fd_index < registered_fds.len);
38743879 try testing.expect(cqe_accept.user_data == accept_userdata);
38753880
......@@ -3911,21 +3916,15 @@ test "accept_direct" {
39113916}
39123917
39133918test "accept_multishot_direct" {
3914 if (builtin.os.tag != .linux) return error.SkipZigTest;
3919 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
39153920
3916 var ring = IO_Uring.init(1, 0) catch |err| switch (err) {
3917 error.SystemOutdated => return error.SkipZigTest,
3918 error.PermissionDenied => return error.SkipZigTest,
3919 else => return err,
3920 };
3921 var ring = try IO_Uring.init(1, 0);
39213922 defer ring.deinit();
3923
39223924 var address = try net.Address.parseIp4("127.0.0.1", 0);
39233925
39243926 var registered_fds = [_]os.fd_t{-1} ** 2;
3925 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
3926 error.FileDescriptorInvalid => return error.SkipZigTest,
3927 else => return err,
3928 };
3927 try ring.register_files(registered_fds[0..]);
39293928
39303929 const listener_socket = try createListenerSocket(&address);
39313930 defer os.closeSocket(listener_socket);
......@@ -3933,7 +3932,7 @@ test "accept_multishot_direct" {
39333932 const accept_userdata: u64 = 0xaaaaaaaa;
39343933
39353934 for (0..2) |_| {
3936 // submit accept
3935 // submit multishot accept
39373936 // Will chose registered fd and return index of the selected registered file in cqe.
39383937 _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0);
39393938 try testing.expectEqual(@as(u32, 1), try ring.submit());
......@@ -3946,7 +3945,6 @@ test "accept_multishot_direct" {
39463945
39473946 // accept completion
39483947 const cqe_accept = try ring.copy_cqe();
3949 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
39503948 const fd_index = cqe_accept.res;
39513949 try testing.expect(fd_index < registered_fds.len);
39523950 try testing.expect(cqe_accept.user_data == accept_userdata);
......@@ -3971,52 +3969,58 @@ test "accept_multishot_direct" {
39713969 try ring.unregister_files();
39723970}
39733971
3974test "socket/socket_direct/socket_direct_alloc/close_direct" {
3975 if (builtin.os.tag != .linux) return error.SkipZigTest;
3972test "socket" {
3973 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
39763974
3977 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
3978 error.SystemOutdated => return error.SkipZigTest,
3979 error.PermissionDenied => return error.SkipZigTest,
3980 else => return err,
3981 };
3975 var ring = try IO_Uring.init(2, 0);
39823976 defer ring.deinit();
3983 var address = try net.Address.parseIp4("127.0.0.1", 0);
39843977
3985 // Below are 4 different ways to get socket fd.
3986 // Two upfront before register_files, and two after
3987 var registered_fds = [_]os.fd_t{-1} ** 4;
3988 // 1. sync syscall socket call
3989 registered_fds[0] = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
3990 // 2. io_uring socket
3991 const socket_userdata = 0xcccccccc;
3992 _ = try ring.socket(socket_userdata, linux.AF.INET, os.SOCK.STREAM, 0, 0);
3978 // prepare, submit socket operation
3979 _ = try ring.socket(0, linux.AF.INET, os.SOCK.STREAM, 0, 0);
3980 try testing.expectEqual(@as(u32, 1), try ring.submit());
3981
3982 // test completion
3983 var cqe = try ring.copy_cqe();
3984 try testing.expectEqual(os.E.SUCCESS, cqe.err());
3985 const fd: os.fd_t = @intCast(cqe.res);
3986 try testing.expect(fd > 2);
3987
3988 os.close(fd);
3989}
3990
3991test "socket_direct/socket_direct_alloc/close_direct" {
3992 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
3993
3994 var ring = try IO_Uring.init(2, 0);
3995 defer ring.deinit();
3996
3997 var registered_fds = [_]os.fd_t{-1} ** 3;
3998 try ring.register_files(registered_fds[0..]);
3999
4000 // create socket in registered file descriptor at index 0 (last param)
4001 _ = try ring.socket_direct(0, linux.AF.INET, os.SOCK.STREAM, 0, 0, 0);
39934002 try testing.expectEqual(@as(u32, 1), try ring.submit());
39944003 var cqe_socket = try ring.copy_cqe();
3995 if (cqe_socket.err() == .INVAL) return error.SkipZigTest;
39964004 try testing.expectEqual(os.E.SUCCESS, cqe_socket.err());
3997 try testing.expect(cqe_socket.res > 2);
3998 registered_fds[1] = cqe_socket.res; // set index 1 to created socket
3999
4000 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
4001 error.FileDescriptorInvalid => return error.SkipZigTest,
4002 else => return err,
4003 };
4005 try testing.expect(cqe_socket.res == 0);
40044006
4005 // 3. io_uring socket_direct, create new socket on index 2
4006 _ = try ring.socket_direct(socket_userdata, linux.AF.INET, os.SOCK.STREAM, 0, 0, @intCast(2));
4007 // create socket in registered file descriptor at index 1 (last param)
4008 _ = try ring.socket_direct(0, linux.AF.INET, os.SOCK.STREAM, 0, 0, 1);
40074009 try testing.expectEqual(@as(u32, 1), try ring.submit());
40084010 cqe_socket = try ring.copy_cqe();
40094011 try testing.expectEqual(os.E.SUCCESS, cqe_socket.err());
4010 try testing.expect(cqe_socket.res == 0);
4012 try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified
40114013
4012 // 4. io_uring socket_direct_alloc
4013 _ = try ring.socket_direct_alloc(socket_userdata, linux.AF.INET, os.SOCK.STREAM, 0, 0);
4014 // create socket in kernel chosen file descriptor index (_alloc version)
4015 // completion res has index from registered files
4016 _ = try ring.socket_direct_alloc(0, linux.AF.INET, os.SOCK.STREAM, 0, 0);
40144017 try testing.expectEqual(@as(u32, 1), try ring.submit());
40154018 cqe_socket = try ring.copy_cqe();
40164019 try testing.expectEqual(os.E.SUCCESS, cqe_socket.err());
4017 try testing.expect(cqe_socket.res == 3);
4020 try testing.expect(cqe_socket.res == 2); // returns registered file index
40184021
40194022 // use sockets from registered_fds in connect operation
4023 var address = try net.Address.parseIp4("127.0.0.1", 0);
40204024 const listener_socket = try createListenerSocket(&address);
40214025 defer os.closeSocket(listener_socket);
40224026 const accept_userdata: u64 = 0xaaaaaaaa;
......@@ -4027,14 +4031,12 @@ test "socket/socket_direct/socket_direct_alloc/close_direct" {
40274031 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
40284032 // prepare connect with fixed socket
40294033 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), &address.any, address.getOsSockLen());
4030 connect_sqe.flags |= linux.IOSQE_FIXED_FILE;
4034 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
40314035 // submit both
40324036 try testing.expectEqual(@as(u32, 2), try ring.submit());
40334037 // get completions
40344038 var cqe_connect = try ring.copy_cqe();
4035 if (cqe_connect.err() == .INVAL) return error.SkipZigTest;
40364039 var cqe_accept = try ring.copy_cqe();
4037 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
40384040 // ignore order
40394041 if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) {
40404042 const a = cqe_accept;
......@@ -4049,7 +4051,7 @@ test "socket/socket_direct/socket_direct_alloc/close_direct" {
40494051 try testing.expect(cqe_accept.user_data == accept_userdata);
40504052 try testing.expectEqual(os.E.SUCCESS, cqe_accept.err());
40514053
4052 // submit and test close completion
4054 // submit and test close_direct
40534055 _ = try ring.close_direct(close_userdata, @intCast(fd_index));
40544056 try testing.expectEqual(@as(u32, 1), try ring.submit());
40554057 var cqe_close = try ring.copy_cqe();
......@@ -4061,20 +4063,13 @@ test "socket/socket_direct/socket_direct_alloc/close_direct" {
40614063}
40624064
40634065test "openat_direct/close_direct" {
4064 if (builtin.os.tag != .linux) return error.SkipZigTest;
4066 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
40654067
4066 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
4067 error.SystemOutdated => return error.SkipZigTest,
4068 error.PermissionDenied => return error.SkipZigTest,
4069 else => return err,
4070 };
4068 var ring = try IO_Uring.init(2, 0);
40714069 defer ring.deinit();
40724070
40734071 var registered_fds = [_]os.fd_t{-1} ** 3;
4074 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
4075 error.FileDescriptorInvalid => return error.SkipZigTest,
4076 else => return err,
4077 };
4072 try ring.register_files(registered_fds[0..]);
40784073
40794074 var tmp = std.testing.tmpDir(.{});
40804075 defer tmp.cleanup();
......@@ -4087,8 +4082,6 @@ test "openat_direct/close_direct" {
40874082 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 0);
40884083 try testing.expectEqual(@as(u32, 1), try ring.submit());
40894084 var cqe = try ring.copy_cqe();
4090 if (cqe.err() == .INVAL) return error.SkipZigTest;
4091 if (cqe.res != 0) return error.SkipZigTest; // old kernel fallback to openat without direct
40924085 try testing.expectEqual(os.E.SUCCESS, cqe.err());
40934086 try testing.expect(cqe.res == 0);
40944087
......@@ -4103,7 +4096,6 @@ test "openat_direct/close_direct" {
41034096 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC);
41044097 try testing.expectEqual(@as(u32, 1), try ring.submit());
41054098 cqe = try ring.copy_cqe();
4106 if (cqe.err() == .INVAL) return error.SkipZigTest; // kernel 5.15 bug
41074099 try testing.expectEqual(os.E.SUCCESS, cqe.err());
41084100 try testing.expect(cqe.res == 2); // chosen index is in res
41094101
......@@ -4116,3 +4108,20 @@ test "openat_direct/close_direct" {
41164108 }
41174109 try ring.unregister_files();
41184110}
4111
4112/// For use in tests. Returns SkipZigTest is kernel version is less than required.
4113fn skipKernelLessThan(required: std.SemanticVersion) !void {
4114 if (builtin.os.tag != .linux) return error.SkipZigTest;
4115
4116 var uts: linux.utsname = undefined;
4117 const res = linux.uname(&uts);
4118 switch (linux.getErrno(res)) {
4119 .SUCCESS => {},
4120 else => |errno| return os.unexpectedErrno(errno),
4121 }
4122
4123 const release = mem.sliceTo(&uts.release, 0);
4124 var current = try std.SemanticVersion.parse(release);
4125 current.pre = null; // don't check pre field
4126 if (required.order(current) == .gt) return error.SkipZigTest;
4127}