authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-10 15:45:38-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-10 15:45:38-05:00
log5a5389128d3d74f02d3697dd317b977c01c5e8c6
treeb5d90f6095f4dc186a7c967ede07da9b6ebae15f
parentafc21a2f1c34912f7cb8f18853f56de04e74f1fc
parentb5a9fd9f98b65aea174700caa01e026e9225dfd7
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7369 from jorangreef/io_uring_timeout

Add io_uring TIMEOUT and TIMEOUT_REMOVE operations:

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

lib/std/os/linux/io_uring.zig+184
......@@ -512,6 +512,52 @@ pub const IO_Uring = struct {
512512 return sqe;
513513 }
514514
515 /// Queues (but does not submit) an SQE to register a timeout operation.
516 /// Returns a pointer to the SQE.
517 ///
518 /// The timeout will complete when either the timeout expires, or after the specified number of
519 /// events complete (if `count` is greater than `0`).
520 ///
521 /// `flags` may be `0` for a relative timeout, or `IORING_TIMEOUT_ABS` for an absolute timeout.
522 ///
523 /// The completion event result will be `-ETIME` if the timeout completed through expiration,
524 /// `0` if the timeout completed after the specified number of events, or `-ECANCELED` if the
525 /// timeout was removed before it expired.
526 ///
527 /// io_uring timeouts use the `CLOCK_MONOTONIC` clock source.
528 pub fn timeout(
529 self: *IO_Uring,
530 user_data: u64,
531 ts: *const os.timespec,
532 count: u32,
533 flags: u32,
534 ) !*io_uring_sqe {
535 const sqe = try self.get_sqe();
536 io_uring_prep_timeout(sqe, ts, count, flags);
537 sqe.user_data = user_data;
538 return sqe;
539 }
540
541 /// Queues (but does not submit) an SQE to remove an existing timeout operation.
542 /// Returns a pointer to the SQE.
543 ///
544 /// The timeout is identified by its `user_data`.
545 ///
546 /// The completion event result will be `0` if the timeout was found and cancelled successfully,
547 /// `-EBUSY` if the timeout was found but expiration was already in progress, or
548 /// `-ENOENT` if the timeout was not found.
549 pub fn timeout_remove(
550 self: *IO_Uring,
551 user_data: u64,
552 timeout_user_data: u64,
553 flags: u32,
554 ) !*io_uring_sqe {
555 const sqe = try self.get_sqe();
556 io_uring_prep_timeout_remove(sqe, timeout_user_data, flags);
557 sqe.user_data = user_data;
558 return sqe;
559 }
560
515561 /// Registers an array of file descriptors.
516562 /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must
517563 /// retrieve a reference to the file, and once I/O has completed the file reference must be
......@@ -822,6 +868,34 @@ pub fn io_uring_prep_close(sqe: *io_uring_sqe, fd: os.fd_t) void {
822868 };
823869}
824870
871pub fn io_uring_prep_timeout(
872 sqe: *io_uring_sqe,
873 ts: *const os.timespec,
874 count: u32,
875 flags: u32,
876) void {
877 io_uring_prep_rw(.TIMEOUT, sqe, -1, ts, 1, count);
878 sqe.rw_flags = flags;
879}
880
881pub fn io_uring_prep_timeout_remove(sqe: *io_uring_sqe, timeout_user_data: u64, flags: u32) void {
882 sqe.* = .{
883 .opcode = .TIMEOUT_REMOVE,
884 .flags = 0,
885 .ioprio = 0,
886 .fd = -1,
887 .off = 0,
888 .addr = timeout_user_data,
889 .len = 0,
890 .rw_flags = flags,
891 .user_data = 0,
892 .buf_index = 0,
893 .personality = 0,
894 .splice_fd_in = 0,
895 .__pad2 = [2]u64{ 0, 0 },
896 };
897}
898
825899test "structs/offsets/entries" {
826900 if (builtin.os.tag != .linux) return error.SkipZigTest;
827901
......@@ -1214,3 +1288,113 @@ test "accept/connect/send/recv" {
12141288
12151289 testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
12161290}
1291
1292test "timeout (after a relative time)" {
1293 if (builtin.os.tag != .linux) return error.SkipZigTest;
1294
1295 var ring = IO_Uring.init(1, 0) catch |err| switch (err) {
1296 error.SystemOutdated => return error.SkipZigTest,
1297 error.PermissionDenied => return error.SkipZigTest,
1298 else => return err,
1299 };
1300 defer ring.deinit();
1301
1302 const ms = 10;
1303 const margin = 5;
1304 const ts = os.timespec{ .tv_sec = 0, .tv_nsec = ms * 1000000 };
1305
1306 const started = std.time.milliTimestamp();
1307 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
1308 testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
1309 testing.expectEqual(@as(u32, 1), try ring.submit());
1310 const cqe = try ring.copy_cqe();
1311 const stopped = std.time.milliTimestamp();
1312
1313 testing.expectEqual(linux.io_uring_cqe{
1314 .user_data = 0x55555555,
1315 .res = -linux.ETIME,
1316 .flags = 0,
1317 }, cqe);
1318 testing.expectWithinMargin(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin);
1319}
1320
1321test "timeout (after a number of completions)" {
1322 if (builtin.os.tag != .linux) return error.SkipZigTest;
1323
1324 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
1325 error.SystemOutdated => return error.SkipZigTest,
1326 error.PermissionDenied => return error.SkipZigTest,
1327 else => return err,
1328 };
1329 defer ring.deinit();
1330
1331 const ts = os.timespec{ .tv_sec = 3, .tv_nsec = 0 };
1332 const count_completions: u64 = 1;
1333 const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0);
1334 testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
1335 testing.expectEqual(count_completions, sqe_timeout.off);
1336 _ = try ring.nop(0x77777777);
1337 testing.expectEqual(@as(u32, 2), try ring.submit());
1338
1339 const cqe_nop = try ring.copy_cqe();
1340 testing.expectEqual(linux.io_uring_cqe{
1341 .user_data = 0x77777777,
1342 .res = 0,
1343 .flags = 0,
1344 }, cqe_nop);
1345
1346 const cqe_timeout = try ring.copy_cqe();
1347 testing.expectEqual(linux.io_uring_cqe{
1348 .user_data = 0x66666666,
1349 .res = 0,
1350 .flags = 0,
1351 }, cqe_timeout);
1352}
1353
1354test "timeout_remove" {
1355 if (builtin.os.tag != .linux) return error.SkipZigTest;
1356
1357 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
1358 error.SystemOutdated => return error.SkipZigTest,
1359 error.PermissionDenied => return error.SkipZigTest,
1360 else => return err,
1361 };
1362 defer ring.deinit();
1363
1364 const ts = os.timespec{ .tv_sec = 3, .tv_nsec = 0 };
1365 const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0);
1366 testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
1367 testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data);
1368
1369 const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0);
1370 testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode);
1371 testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr);
1372 testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data);
1373
1374 testing.expectEqual(@as(u32, 2), try ring.submit());
1375
1376 const cqe_timeout = try ring.copy_cqe();
1377 // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version:
1378 // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL.
1379 // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6.
1380 // We don't want to skip this test for newer kernels.
1381 if (
1382 cqe_timeout.user_data == 0x99999999 and
1383 cqe_timeout.res == -linux.EBADF and
1384 (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0
1385 ) {
1386 return error.SkipZigTest;
1387 }
1388 testing.expectEqual(linux.io_uring_cqe{
1389 .user_data = 0x88888888,
1390 .res = -linux.ECANCELED,
1391 .flags = 0,
1392 }, cqe_timeout);
1393
1394 const cqe_timeout_remove = try ring.copy_cqe();
1395 testing.expectEqual(linux.io_uring_cqe{
1396 .user_data = 0x99999999,
1397 .res = 0,
1398 .flags = 0,
1399 }, cqe_timeout_remove);
1400}