authorgravatar for vincent@rischmann.frVincent Rischmann <vincent@rischmann.fr> 2022-05-16 21:54:29+02:00
committergravatar for vincent@rischmann.frVincent Rischmann <vincent@rischmann.fr> 2022-05-25 13:53:09+02:00
logacb8af468f496dadbc7550196d7ba05fb8efd586
treefeab1fa2f521a53fc7c39ee56c00fc2dbc4c3a19
parentd8798ef0cd22532d26157aa1939b96aabc02223c

io_uring: add tests for automatic buffer selection


1 files changed, 384 insertions(+), 0 deletions(-)

lib/std/os/linux/io_uring.zig+384
......@@ -2830,6 +2830,390 @@ test "linkat" {
28302830 try testing.expectEqualStrings("hello", second_file_data[0..read]);
28312831}
28322832
2833test "provide_buffers: read" {
2834 if (builtin.os.tag != .linux) return error.SkipZigTest;
2835
2836 var ring = IO_Uring.init(1, 0) catch |err| switch (err) {
2837 error.SystemOutdated => return error.SkipZigTest,
2838 error.PermissionDenied => return error.SkipZigTest,
2839 else => return err,
2840 };
2841 defer ring.deinit();
2842
2843 const fd = try os.openZ("/dev/zero", os.O.RDONLY | os.O.CLOEXEC, 0);
2844 defer os.close(fd);
2845
2846 const group_id = 1337;
2847 const buffer_id = 0;
2848
2849 const buffer_len = 128;
2850
2851 var buffers: [4][buffer_len]u8 = undefined;
2852
2853 // Provide 4 buffers
2854
2855 {
2856 const sqe = try ring.provide_buffers(0xcccccccc, @ptrCast([*]u8, &buffers), buffers.len, buffer_len, group_id, buffer_id);
2857 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
2858 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
2859 try testing.expectEqual(@as(u32, buffers[0].len), sqe.len);
2860 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
2861 try testing.expectEqual(@as(u32, 1), try ring.submit());
2862
2863 const cqe = try ring.copy_cqe();
2864 switch (cqe.err()) {
2865 // Happens when the kernel is < 5.7
2866 .INVAL => return error.SkipZigTest,
2867 .SUCCESS => {},
2868 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2869 }
2870 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
2871 }
2872
2873 // Do 4 reads which should consume all buffers
2874
2875 var i: usize = 0;
2876 while (i < buffers.len) : (i += 1) {
2877 var sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
2878 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2879 try testing.expectEqual(@as(i32, fd), sqe.fd);
2880 try testing.expectEqual(@as(u64, 0), sqe.addr);
2881 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
2882 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
2883 try testing.expectEqual(@as(u32, 1), try ring.submit());
2884
2885 const cqe = try ring.copy_cqe();
2886 switch (cqe.err()) {
2887 .SUCCESS => {},
2888 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2889 }
2890
2891 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
2892 const used_buffer_id = cqe.flags >> 16;
2893 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
2894 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
2895
2896 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
2897 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@intCast(usize, cqe.res)]);
2898 }
2899
2900 // This read should fail
2901
2902 {
2903 var sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
2904 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2905 try testing.expectEqual(@as(i32, fd), sqe.fd);
2906 try testing.expectEqual(@as(u64, 0), sqe.addr);
2907 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
2908 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
2909 try testing.expectEqual(@as(u32, 1), try ring.submit());
2910
2911 const cqe = try ring.copy_cqe();
2912 switch (cqe.err()) {
2913 // Expected
2914 .NOBUFS => {},
2915 .SUCCESS => std.debug.panic("unexpected success", .{}),
2916 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2917 }
2918 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
2919 }
2920
2921 // Provide 1 buffer again
2922
2923 // Deliberately put something we don't expect in the buffers
2924 mem.set(u8, mem.sliceAsBytes(&buffers), 42);
2925
2926 const reprovided_buffer_id = 2;
2927
2928 {
2929 _ = try ring.provide_buffers(0xabababab, @ptrCast([*]u8, &buffers[reprovided_buffer_id]), 1, buffer_len, group_id, reprovided_buffer_id);
2930 try testing.expectEqual(@as(u32, 1), try ring.submit());
2931
2932 const cqe = try ring.copy_cqe();
2933 switch (cqe.err()) {
2934 .SUCCESS => {},
2935 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2936 }
2937 }
2938
2939 // Final read which should work
2940
2941 {
2942 var sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
2943 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2944 try testing.expectEqual(@as(i32, fd), sqe.fd);
2945 try testing.expectEqual(@as(u64, 0), sqe.addr);
2946 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
2947 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
2948 try testing.expectEqual(@as(u32, 1), try ring.submit());
2949
2950 const cqe = try ring.copy_cqe();
2951 switch (cqe.err()) {
2952 .SUCCESS => {},
2953 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2954 }
2955
2956 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
2957 const used_buffer_id = cqe.flags >> 16;
2958 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
2959 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
2960 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
2961 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@intCast(usize, cqe.res)]);
2962 }
2963}
2964
2965test "remove_buffers" {
2966 if (builtin.os.tag != .linux) return error.SkipZigTest;
2967
2968 var ring = IO_Uring.init(1, 0) catch |err| switch (err) {
2969 error.SystemOutdated => return error.SkipZigTest,
2970 error.PermissionDenied => return error.SkipZigTest,
2971 else => return err,
2972 };
2973 defer ring.deinit();
2974
2975 const fd = try os.openZ("/dev/zero", os.O.RDONLY | os.O.CLOEXEC, 0);
2976 defer os.close(fd);
2977
2978 const group_id = 1337;
2979 const buffer_id = 0;
2980
2981 const buffer_len = 128;
2982
2983 var buffers: [4][buffer_len]u8 = undefined;
2984
2985 // Provide 4 buffers
2986
2987 {
2988 _ = try ring.provide_buffers(0xcccccccc, @ptrCast([*]u8, &buffers), buffers.len, buffer_len, group_id, buffer_id);
2989 try testing.expectEqual(@as(u32, 1), try ring.submit());
2990
2991 const cqe = try ring.copy_cqe();
2992 switch (cqe.err()) {
2993 .SUCCESS => {},
2994 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2995 }
2996 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
2997 }
2998
2999 // Remove the first 3 buffers
3000
3001 {
3002 var sqe = try ring.remove_buffers(0xbababababa, 3, group_id);
3003 try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode);
3004 try testing.expectEqual(@as(i32, 3), sqe.fd);
3005 try testing.expectEqual(@as(u64, 0), sqe.addr);
3006 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3007 try testing.expectEqual(@as(u32, 1), try ring.submit());
3008
3009 const cqe = try ring.copy_cqe();
3010 switch (cqe.err()) {
3011 .SUCCESS => {},
3012 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3013 }
3014 try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data);
3015 }
3016
3017 // This read should work
3018
3019 {
3020 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3021 try testing.expectEqual(@as(u32, 1), try ring.submit());
3022
3023 const cqe = try ring.copy_cqe();
3024 switch (cqe.err()) {
3025 .SUCCESS => {},
3026 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3027 }
3028
3029 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3030 const used_buffer_id = cqe.flags >> 16;
3031 try testing.expectEqual(used_buffer_id, 0);
3032 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3033 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3034 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@intCast(usize, cqe.res)]);
3035 }
3036
3037 // Final read should _not_ work
3038
3039 {
3040 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3041 try testing.expectEqual(@as(u32, 1), try ring.submit());
3042
3043 const cqe = try ring.copy_cqe();
3044 switch (cqe.err()) {
3045 // Expected
3046 .NOBUFS => {},
3047 .SUCCESS => std.debug.panic("unexpected success", .{}),
3048 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3049 }
3050 }
3051}
3052
3053test "provide_buffers: accept/connect/send/recv" {
3054 if (builtin.os.tag != .linux) return error.SkipZigTest;
3055
3056 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
3057 error.SystemOutdated => return error.SkipZigTest,
3058 error.PermissionDenied => return error.SkipZigTest,
3059 else => return err,
3060 };
3061 defer ring.deinit();
3062
3063 const group_id = 1337;
3064 const buffer_id = 0;
3065
3066 const buffer_len = 128;
3067 var buffers: [4][buffer_len]u8 = undefined;
3068
3069 // Provide 4 buffers
3070
3071 {
3072 const sqe = try ring.provide_buffers(0xcccccccc, @ptrCast([*]u8, &buffers), buffers.len, buffer_len, group_id, buffer_id);
3073 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
3074 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
3075 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3076 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3077 try testing.expectEqual(@as(u32, 1), try ring.submit());
3078
3079 const cqe = try ring.copy_cqe();
3080 switch (cqe.err()) {
3081 // Happens when the kernel is < 5.7
3082 .INVAL => return error.SkipZigTest,
3083 .SUCCESS => {},
3084 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3085 }
3086 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
3087 }
3088
3089 const socket_test_harness = try createSocketTestHarness(&ring);
3090 defer socket_test_harness.close();
3091
3092 // Do 4 send on the socket
3093
3094 {
3095 var i: usize = 0;
3096 while (i < buffers.len) : (i += 1) {
3097 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'z'} ** buffer_len), 0);
3098 try testing.expectEqual(@as(u32, 1), try ring.submit());
3099 }
3100
3101 var cqes: [4]linux.io_uring_cqe = undefined;
3102 try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4));
3103 }
3104
3105 // Do 4 recv which should consume all buffers
3106
3107 // Deliberately put something we don't expect in the buffers
3108 mem.set(u8, mem.sliceAsBytes(&buffers), 1);
3109
3110 var i: usize = 0;
3111 while (i < buffers.len) : (i += 1) {
3112 var sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3113 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3114 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3115 try testing.expectEqual(@as(u64, 0), sqe.addr);
3116 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3117 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3118 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3119 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3120 try testing.expectEqual(@as(u32, 1), try ring.submit());
3121
3122 const cqe = try ring.copy_cqe();
3123 switch (cqe.err()) {
3124 .SUCCESS => {},
3125 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3126 }
3127
3128 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3129 const used_buffer_id = cqe.flags >> 16;
3130 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
3131 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3132
3133 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
3134 const buffer = buffers[used_buffer_id][0..@intCast(usize, cqe.res)];
3135 try testing.expectEqualSlices(u8, &([_]u8{'z'} ** buffer_len), buffer);
3136 }
3137
3138 // This recv should fail
3139
3140 {
3141 var sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3142 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3143 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3144 try testing.expectEqual(@as(u64, 0), sqe.addr);
3145 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3146 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3147 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3148 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3149 try testing.expectEqual(@as(u32, 1), try ring.submit());
3150
3151 const cqe = try ring.copy_cqe();
3152 switch (cqe.err()) {
3153 // Expected
3154 .NOBUFS => {},
3155 .SUCCESS => std.debug.panic("unexpected success", .{}),
3156 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3157 }
3158 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3159 }
3160
3161 // Provide 1 buffer again
3162
3163 const reprovided_buffer_id = 2;
3164
3165 {
3166 _ = try ring.provide_buffers(0xabababab, @ptrCast([*]u8, &buffers[reprovided_buffer_id]), 1, buffer_len, group_id, reprovided_buffer_id);
3167 try testing.expectEqual(@as(u32, 1), try ring.submit());
3168
3169 const cqe = try ring.copy_cqe();
3170 switch (cqe.err()) {
3171 .SUCCESS => {},
3172 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3173 }
3174 }
3175
3176 // Redo 1 send on the server socket
3177
3178 {
3179 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'w'} ** buffer_len), 0);
3180 try testing.expectEqual(@as(u32, 1), try ring.submit());
3181
3182 _ = try ring.copy_cqe();
3183 }
3184
3185 // Final recv which should work
3186
3187 // Deliberately put something we don't expect in the buffers
3188 mem.set(u8, mem.sliceAsBytes(&buffers), 1);
3189
3190 {
3191 var sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3192 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3193 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3194 try testing.expectEqual(@as(u64, 0), sqe.addr);
3195 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3196 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3197 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3198 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3199 try testing.expectEqual(@as(u32, 1), try ring.submit());
3200
3201 const cqe = try ring.copy_cqe();
3202 switch (cqe.err()) {
3203 .SUCCESS => {},
3204 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3205 }
3206
3207 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3208 const used_buffer_id = cqe.flags >> 16;
3209 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
3210 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3211 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3212 const buffer = buffers[used_buffer_id][0..@intCast(usize, cqe.res)];
3213 try testing.expectEqualSlices(u8, &([_]u8{'w'} ** buffer_len), buffer);
3214 }
3215}
3216
28333217/// Used for testing server/client interactions.
28343218const SocketTestHarness = struct {
28353219 listener: os.socket_t,