| ... | @@ -512,6 +512,52 @@ pub const IO_Uring = struct { | ... | @@ -512,6 +512,52 @@ pub const IO_Uring = struct { |
| 512 | return sqe; | 512 | return sqe; |
| 513 | } | 513 | } |
| 514 | | 514 | |
| | 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 | |
| 515 | /// Registers an array of file descriptors. | 561 | /// Registers an array of file descriptors. |
| 516 | /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must | 562 | /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must |
| 517 | /// retrieve a reference to the file, and once I/O has completed the file reference must be | 563 | /// 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 { | ... | @@ -822,6 +868,34 @@ pub fn io_uring_prep_close(sqe: *io_uring_sqe, fd: os.fd_t) void { |
| 822 | }; | 868 | }; |
| 823 | } | 869 | } |
| 824 | | 870 | |
| | 871 | pub 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 | |
| | 881 | pub 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 | |
| 825 | test "structs/offsets/entries" { | 899 | test "structs/offsets/entries" { |
| 826 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 900 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 827 | | 901 | |
| ... | @@ -1214,3 +1288,113 @@ test "accept/connect/send/recv" { | ... | @@ -1214,3 +1288,113 @@ test "accept/connect/send/recv" { |
| 1214 | | 1288 | |
| 1215 | testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); | 1289 | testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); |
| 1216 | } | 1290 | } |
| | 1291 | |
| | 1292 | test "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 | |
| | 1321 | test "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 | |
| | 1354 | test "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 | } |