air_sys_syscall/io_uring/
multishot.rs1use super::owned::OwnedOp;
30use super::raw::{self, IoUringSqe};
31use super::{CancelTarget, IoUring, MultishotToken, ProvidedBufferRing, SubmitOptions};
32use air_sys_types::Errno;
33use air_sys_types::fd::{AsRawFd, BorrowedFd};
34use air_sys_types::io_uring::{PollEvents, TimeoutFlags, TimeoutSpec};
35use air_sys_types::net::{AcceptFlags, MessageFlags};
36use alloc::boxed::Box;
37use core::time::Duration;
38
39impl IoUring {
40 fn submit_op_multishot(
46 &mut self,
47 payload: Option<OwnedOp>,
48 fill: impl FnOnce(&mut IoUringSqe),
49 ) -> Result<MultishotToken, Errno> {
50 if self.sq.space_left() == 0 {
51 return Err(Errno::EBUSY);
52 }
53 let token = self
54 .slab
55 .reserve_multishot(payload)
56 .map_err(|_| Errno::EBUSY)?;
57 let opts = self.pending_options;
58 self.pending_options = SubmitOptions::default();
59 let personality = self.pending_personality;
60 self.pending_personality = 0;
61 let sqe_ptr = unsafe { self.sq.prepare() }.expect("place SQ vérifiée");
64 unsafe {
67 let sqe = &mut *sqe_ptr;
68 fill(sqe);
69 sqe.user_data = token.to_user_data();
70 sqe.personality = personality;
71 sqe.flags = opts.iosqe_flags();
72 }
73 Ok(MultishotToken::new(token.slot(), token.generation()))
74 }
75
76 pub fn submit_accept_multishot(
87 &mut self,
88 listener: BorrowedFd<'_>,
89 flags: AcceptFlags,
90 ) -> Result<MultishotToken, Errno> {
91 let fd = listener.as_raw_fd();
92 let accept_flags = (flags | AcceptFlags::CLOEXEC).bits().cast_unsigned();
93 self.submit_op_multishot(None, |sqe| {
94 sqe.opcode = raw::IORING_OP_ACCEPT;
95 sqe.fd = fd;
96 sqe.op_flags = accept_flags;
97 sqe.ioprio |= raw::IORING_ACCEPT_MULTISHOT;
98 })
99 }
100
101 pub fn submit_accept_multishot_direct(
114 &mut self,
115 listener: BorrowedFd<'_>,
116 flags: AcceptFlags,
117 ) -> Result<MultishotToken, Errno> {
118 let fd = listener.as_raw_fd();
119 let accept_flags = flags.bits().cast_unsigned();
120 self.submit_op_multishot(None, |sqe| {
121 sqe.opcode = raw::IORING_OP_ACCEPT;
122 sqe.fd = fd;
123 sqe.op_flags = accept_flags;
124 sqe.ioprio |= raw::IORING_ACCEPT_MULTISHOT;
125 sqe.splice_fd_in_or_file_index = raw::IORING_FILE_INDEX_ALLOC;
127 })
128 }
129
130 pub fn submit_receive_multishot(
146 &mut self,
147 sock: BorrowedFd<'_>,
148 group: &ProvidedBufferRing,
149 flags: MessageFlags,
150 ) -> Result<MultishotToken, Errno> {
151 let fd = sock.as_raw_fd();
152 let bgid = group.group_id();
153 let msg_flags = flags.bits().cast_unsigned();
154 self.pending_options = self.pending_options.buffer_select();
155 self.submit_op_multishot(None, |sqe| {
156 sqe.opcode = raw::IORING_OP_RECV;
157 sqe.fd = fd;
158 sqe.op_flags = msg_flags;
159 sqe.buf_index_or_group = bgid;
160 sqe.ioprio |= raw::IORING_RECV_MULTISHOT;
161 })
162 }
163
164 pub fn submit_read_multishot(
173 &mut self,
174 fd: BorrowedFd<'_>,
175 group: &ProvidedBufferRing,
176 offset: Option<u64>,
177 ) -> Result<MultishotToken, Errno> {
178 let fd = fd.as_raw_fd();
179 let bgid = group.group_id();
180 let off = offset.unwrap_or(u64::MAX);
181 self.pending_options = self.pending_options.buffer_select();
182 self.submit_op_multishot(None, |sqe| {
183 sqe.opcode = raw::IORING_OP_READ_MULTISHOT;
184 sqe.fd = fd;
185 sqe.off_or_addr2 = off;
186 sqe.buf_index_or_group = bgid;
187 })
188 }
189
190 pub fn submit_poll_multishot(
202 &mut self,
203 fd: BorrowedFd<'_>,
204 events: PollEvents,
205 ) -> Result<MultishotToken, Errno> {
206 let fd = fd.as_raw_fd();
207 let poll_events = events.bits();
208 self.submit_op_multishot(None, |sqe| {
209 sqe.opcode = raw::IORING_OP_POLL_ADD;
210 sqe.fd = fd;
211 sqe.op_flags = poll_events; sqe.len = raw::IORING_POLL_ADD_MULTI; })
214 }
215
216 pub fn submit_timeout_multishot(
228 &mut self,
229 interval: Duration,
230 flags: TimeoutFlags,
231 ) -> Result<MultishotToken, Errno> {
232 let ts = Box::new(super::async_ops::timespec_of(TimeoutSpec::after(interval)));
233 let ts_ptr = core::ptr::from_ref(&*ts) as u64;
234 let op_flags = flags.bits() | raw::IORING_TIMEOUT_MULTISHOT;
235 self.submit_op_multishot(Some(OwnedOp::Timeout(ts)), |sqe| {
236 sqe.opcode = raw::IORING_OP_TIMEOUT;
237 sqe.fd = -1;
238 sqe.addr_or_splice_off_in = ts_ptr;
239 sqe.len = 1;
240 sqe.op_flags = op_flags;
241 })
242 }
243
244 pub fn cancel_multishot(&mut self, token: MultishotToken) -> Result<(), Errno> {
260 self.sync_cancel(CancelTarget::Token(token.as_submission()))
261 .map(|_count| ())
262 }
263}
264
265#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::io_uring::{
278 Completion, FixedFdTable, IoUring, ProvidedBufferRing, ProvidedBufferRingOptions,
279 };
280 use crate::test_support::{sfd, std_file};
281 use air_sys_types::fd::AsFd;
282 use air_sys_types::io_uring::PollEvents;
283 use air_sys_types::ipc::PipeFlags;
284 use core::num::{NonZeroU16, NonZeroU32};
285 use std::io::Write;
286
287 fn ring8() -> IoUring {
288 IoUring::new(NonZeroU32::new(8).expect("8 ≠ 0")).expect("io_uring_setup 6.12")
289 }
290
291 fn drain_n(ring: &mut IoUring, n: u32) -> Vec<Completion> {
292 ring.submit_and_wait(n).expect("submit_and_wait");
293 let mut out = Vec::new();
294 while out.len() < usize::try_from(n).expect("n ⊆ usize") {
295 out.push(ring.wait_completion().expect("wait_completion"));
296 }
297 out
298 }
299
300 #[test]
303 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
304 fn accept_multishot_streams_connections_then_cancel() {
305 let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
306 let addr = listener.local_addr().expect("addr");
307 let _c1 = std::net::TcpStream::connect(addr).expect("connect 1");
309 let _c2 = std::net::TcpStream::connect(addr).expect("connect 2");
310 std::thread::sleep(std::time::Duration::from_millis(20));
311 let mut ring = ring8();
312 let token = ring
313 .submit_accept_multishot(sfd(&listener), AcceptFlags::empty())
314 .expect("accept_multishot");
315 let completions = drain_n(&mut ring, 2);
317 for c in completions {
318 assert_eq!(c.multishot_token(), Some(token), "même jeton multishot");
319 assert!(c.has_more(), "F_MORE : flux maintenu");
320 let _fd = c.accepted_fd().expect("FD accepté valide");
321 }
322 assert_eq!(ring.in_flight(), 1, "slot multishot vivant tant que F_MORE");
323 ring.cancel_multishot(token).expect("cancel_multishot");
325 let terminal = ring.wait_completion().expect("terminale");
326 assert_eq!(terminal.multishot_token(), Some(token));
327 assert!(!terminal.has_more(), "terminale : pas de F_MORE");
328 assert_eq!(terminal.into_result(), Err(Errno::ECANCELED));
329 assert_eq!(ring.in_flight(), 0, "slot libéré à la terminale");
330 }
331
332 #[test]
333 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
334 fn accept_multishot_direct_lands_in_fixed_slots() {
335 let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
336 let addr = listener.local_addr().expect("addr");
337 let _c1 = std::net::TcpStream::connect(addr).expect("connect");
338 std::thread::sleep(std::time::Duration::from_millis(20));
339 let mut ring = ring8();
340 let table = FixedFdTable::register(&mut ring, NonZeroU32::new(4).expect("4"))
341 .expect("register table");
342 let token = ring
343 .submit_accept_multishot_direct(sfd(&listener), AcceptFlags::empty())
344 .expect("accept_multishot_direct");
345 let completions = drain_n(&mut ring, 1);
346 let c = completions.into_iter().next().expect("une complétion");
347 assert_eq!(c.multishot_token(), Some(token));
348 assert!(c.has_more());
349 let slot = c.allocated_slot().expect("slot auto-alloué");
350 assert!(slot < 4, "connexion dans un slot fixe");
351 ring.cancel_multishot(token).expect("cancel");
352 let terminal = ring.wait_completion().expect("terminale");
353 assert!(!terminal.has_more());
354 table.unregister(&mut ring).expect("unregister");
355 }
356
357 #[test]
360 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
361 fn recv_multishot_streams_buffers() {
362 let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
363 let addr = listener.local_addr().expect("addr");
364 let mut client = std::net::TcpStream::connect(addr).expect("connect");
365 let (server, _) = listener.accept().expect("accept");
366 client.write_all(b"hello").expect("write");
367 client.flush().expect("flush");
368 std::thread::sleep(std::time::Duration::from_millis(20));
369 let mut ring = ring8();
370 let mut group = ProvidedBufferRing::register(
371 &mut ring,
372 1,
373 NonZeroU16::new(4).expect("4"),
374 NonZeroU32::new(64).expect("64"),
375 ProvidedBufferRingOptions::default(),
376 )
377 .expect("register group");
378 let token = ring
379 .submit_receive_multishot(sfd(&server), &group, MessageFlags::empty())
380 .expect("recv_multishot");
381 let completions = drain_n(&mut ring, 1);
382 let c = completions.into_iter().next().expect("une complétion");
383 assert_eq!(c.multishot_token(), Some(token));
384 assert!(c.has_more(), "flux maintenu (groupe non vide)");
385 {
386 let buf = c.into_provided_buffer(&mut group).expect("buffer choisi");
387 assert_eq!(buf.data(), b"hello");
388 } ring.cancel_multishot(token).expect("cancel");
390 let terminal = ring.wait_completion().expect("terminale");
391 assert!(!terminal.has_more());
392 group.unregister(&mut ring).expect("unregister");
393 }
394
395 #[test]
396 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
397 fn recv_multishot_enobufs_terminates_then_resubmit() {
398 let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
399 let addr = listener.local_addr().expect("addr");
400 let mut client = std::net::TcpStream::connect(addr).expect("connect");
401 let (server, _) = listener.accept().expect("accept");
402 client.write_all(b"AAAABBBB").expect("write 8");
404 client.flush().expect("flush");
405 std::thread::sleep(std::time::Duration::from_millis(20));
406 let mut ring = ring8();
407 let mut group = ProvidedBufferRing::register(
408 &mut ring,
409 2,
410 NonZeroU16::new(1).expect("1"),
411 NonZeroU32::new(4).expect("4"),
412 ProvidedBufferRingOptions::default(),
413 )
414 .expect("register count=1");
415 let token = ring
416 .submit_receive_multishot(sfd(&server), &group, MessageFlags::empty())
417 .expect("recv_multishot");
418 ring.submit_and_wait(1).expect("submit");
419 let first = ring.wait_completion().expect("1ère");
421 assert_eq!(first.multishot_token(), Some(token));
422 assert!(first.has_more());
423 {
424 let buf = first.into_provided_buffer(&mut group).expect("buffer");
425 assert_eq!(buf.data(), b"AAAA");
426 } let second = ring.wait_completion().expect("2ᵉ");
429 assert_eq!(second.multishot_token(), Some(token));
430 assert!(!second.has_more(), "ENOBUFS termine le multishot");
431 assert!(
432 second.into_provided_buffer(&mut group).is_none(),
433 "pas de buffer"
434 );
435 assert_eq!(ring.in_flight(), 0, "slot libéré à la terminale ENOBUFS");
436 let token2 = ring
438 .submit_receive_multishot(sfd(&server), &group, MessageFlags::empty())
439 .expect("re-recv_multishot");
440 let again = drain_n(&mut ring, 1)
441 .into_iter()
442 .next()
443 .expect("complétion");
444 assert_eq!(again.multishot_token(), Some(token2));
445 let buf = again.into_provided_buffer(&mut group).expect("buffer 2");
446 assert_eq!(buf.data(), b"BBBB");
447 drop(buf);
448 ring.cancel_multishot(token2).expect("cancel");
449 let _ = ring.wait_completion().expect("terminale");
450 group.unregister(&mut ring).expect("unregister");
451 }
452
453 #[test]
456 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
457 fn poll_multishot_signals_then_cancel() {
458 let (reader, writer) = crate::ipc::pipe2(PipeFlags::empty()).expect("pipe2");
459 let mut w = std_file(writer);
460 w.write_all(b"x").expect("write pipe");
461 w.flush().expect("flush");
462 let mut ring = ring8();
463 let token = ring
464 .submit_poll_multishot(reader.as_fd(), PollEvents::IN)
465 .expect("poll_multishot");
466 let c = drain_n(&mut ring, 1)
467 .into_iter()
468 .next()
469 .expect("complétion");
470 assert_eq!(c.multishot_token(), Some(token));
471 assert!(c.has_more(), "poll multishot maintient F_MORE");
472 let events = c.into_poll_result().expect("événements");
473 assert!(events.contains(PollEvents::IN), "POLLIN signalé");
474 ring.cancel_multishot(token).expect("cancel");
475 let terminal = ring.wait_completion().expect("terminale");
476 assert!(!terminal.has_more());
477 assert_eq!(ring.in_flight(), 0);
478 }
479
480 #[test]
483 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
484 fn timeout_multishot_ticks_then_cancel() {
485 use air_sys_types::io_uring::TimeoutFlags;
486 let mut ring = ring8();
487 let token = ring
488 .submit_timeout_multishot(std::time::Duration::from_millis(20), TimeoutFlags::empty())
489 .expect("timeout_multishot");
490 for _ in 0..2 {
492 let tick = drain_n(&mut ring, 1).into_iter().next().expect("tick");
493 assert_eq!(tick.multishot_token(), Some(token));
494 assert!(tick.has_more(), "tick maintient F_MORE");
495 }
496 assert_eq!(ring.in_flight(), 1, "slot vivant entre les ticks");
497 ring.cancel_multishot(token).expect("cancel");
498 let terminal = ring.wait_completion().expect("terminale");
499 assert!(!terminal.has_more());
500 assert_eq!(ring.in_flight(), 0);
501 }
502
503 #[test]
506 #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
507 fn mono_shot_completion_has_no_multishot_token() {
508 let mut ring = ring8();
509 let _tok = ring.submit_nop().expect("nop");
510 let c = drain_n(&mut ring, 1)
511 .into_iter()
512 .next()
513 .expect("complétion");
514 assert_eq!(
515 c.multishot_token(),
516 None,
517 "op mono-coup : pas de jeton multishot"
518 );
519 assert!(!c.has_more());
520 }
521
522 #[test]
525 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
526 fn read_multishot_streams_buffers_from_pipe() {
527 let (reader, writer) = crate::ipc::pipe2(PipeFlags::empty()).expect("pipe2");
528 let mut w = std_file(writer);
531 w.write_all(b"piped").expect("write pipe");
532 w.flush().expect("flush");
533 let mut ring = ring8();
534 let mut group = ProvidedBufferRing::register(
535 &mut ring,
536 6,
537 NonZeroU16::new(4).expect("4"),
538 NonZeroU32::new(64).expect("64"),
539 ProvidedBufferRingOptions::default(),
540 )
541 .expect("register group");
542 let token = ring
543 .submit_read_multishot(reader.as_fd(), &group, None)
544 .expect("read_multishot");
545 let c = drain_n(&mut ring, 1)
546 .into_iter()
547 .next()
548 .expect("complétion");
549 assert_eq!(c.multishot_token(), Some(token));
550 assert!(c.has_more(), "armé : writer encore ouvert");
551 let buf = c.into_provided_buffer(&mut group).expect("buffer choisi");
552 assert_eq!(buf.data(), b"piped");
553 drop(buf);
554 ring.cancel_multishot(token).expect("cancel");
555 let terminal = ring.wait_completion().expect("terminale");
556 assert!(!terminal.has_more());
557 group.unregister(&mut ring).expect("unregister");
558 drop(w);
559 }
560
561 #[test]
564 #[cfg_attr(miri, ignore = "io_uring multishot non supporté par Miri")]
565 fn multishot_on_full_sq_returns_ebusy() {
566 let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
567 let mut ring = ring8();
568 for _ in 0..8 {
570 ring.submit_nop().expect("nop");
571 }
572 assert_eq!(
574 ring.submit_accept_multishot(sfd(&listener), AcceptFlags::empty()),
575 Err(Errno::EBUSY)
576 );
577 let _ = drain_n(&mut ring, 8);
579 assert_eq!(ring.in_flight(), 0);
580 }
581}