1use super::ioring::IoCompletionRing;
17use super::ioring::IoMemory;
18use super::ioring::IoRing;
19use futures::FutureExt;
20use inspect::Inspect;
21use io_uring::opcode;
22use io_uring::squeue;
23use loan_cell::LoanCell;
24use pal_async::task::Runnable;
25use pal_async::task::Schedule;
26use pal_async::task::Scheduler;
27use pal_async::task::Spawn;
28use pal_async::task::TaskMetadata;
29use pal_async::task::TaskQueue;
30use std::borrow::Borrow;
31use std::cell::Cell;
32use std::cell::RefCell;
33use std::fmt::Debug;
34use std::future::Future;
35use std::future::poll_fn;
36use std::io;
37use std::os::unix::prelude::*;
38use std::pin::Pin;
39use std::pin::pin;
40use std::process::abort;
41use std::sync::Arc;
42use std::task::Context;
43use std::task::Poll;
44use std::task::Wake;
45use std::task::Waker;
46
47pub struct IoUringPool {
49 client: PoolClient,
50 worker: Arc<Worker>,
51 completion_ring: IoCompletionRing,
52 queue: TaskQueue,
53}
54
55impl IoUringPool {
56 pub fn new(name: impl Into<Arc<str>>, ring_size: u32) -> io::Result<Self> {
58 let (queue, scheduler) = pal_async::task::task_queue(name);
59
60 let (io_ring, completion_ring) = IoRing::new(ring_size)?;
61 let worker = Arc::new(Worker::new(ring_size, io_ring));
62
63 Ok(Self {
64 client: PoolClient(worker.clone().initiator(scheduler)),
65 worker,
66 completion_ring,
67 queue,
68 })
69 }
70
71 pub fn client(&self) -> &PoolClient {
73 &self.client
74 }
75
76 pub fn run(mut self) {
79 drop(self.client);
80 self.worker.run(self.completion_ring, self.queue.run())
81 }
82}
83
84#[derive(Debug, Clone, Inspect)]
86#[inspect(transparent)]
87pub struct PoolClient(#[inspect(with = "|x| &x.client")] IoInitiator);
88
89impl PoolClient {
90 pub fn set_idle_task<F>(&self, f: F)
99 where
100 F: 'static + Send + AsyncFnOnce(IdleControl),
101 {
102 let keep_pool_alive = self.clone();
105 let f = Box::new(|fd| {
106 Box::pin(async move {
107 let _keep_pool_alive = keep_pool_alive;
108 f(fd).await
109 }) as Pin<Box<dyn Future<Output = _>>>
110 })
111 as Box<dyn Send + FnOnce(IdleControl) -> Pin<Box<dyn Future<Output = ()>>>>;
112
113 let worker_id = Arc::as_ptr(&self.0.client.worker) as usize; let task = self.0.spawn("set_idle_task", async move {
116 THREADPOOL_WORKER_STATE.with(|state| {
117 state.borrow(|state| {
118 let state = state.unwrap();
119 assert_eq!(Arc::as_ptr(&state.worker), worker_id as *const _);
120 state.new_idle_task.set(Some(f));
121 })
122 })
123 });
124
125 task.detach();
126 }
127
128 pub fn initiator(&self) -> &IoInitiator {
130 &self.0
131 }
132
133 pub fn set_iowq_affinity(&self, affinity: &pal::unix::affinity::CpuSet) -> io::Result<()> {
135 self.0.client.worker.io_ring.set_iowq_affinity(affinity)
136 }
137
138 pub fn set_iowq_max_workers(
141 &self,
142 bounded: Option<u32>,
143 unbounded: Option<u32>,
144 ) -> io::Result<()> {
145 self.0
146 .client
147 .worker
148 .io_ring
149 .set_iowq_max_workers(bounded, unbounded)
150 }
151}
152
153impl Schedule for PoolClient {
154 fn schedule(&self, runnable: Runnable) {
155 self.0.client.schedule(runnable)
156 }
157
158 fn name(&self) -> Arc<str> {
159 self.0.client.name()
160 }
161}
162
163#[derive(Debug, inspect::Inspect)]
164pub(crate) struct Worker {
165 io_ring_size: u32,
166 io_ring: IoRing,
167}
168
169type IdleTask = Pin<Box<dyn Future<Output = ()>>>;
170type IdleTaskSpawn = Box<dyn Send + FnOnce(IdleControl) -> IdleTask>;
171
172struct AffinitizedWorkerState {
173 worker: Arc<Worker>,
174 wake: Cell<bool>,
175 new_idle_task: Cell<Option<IdleTaskSpawn>>,
176 completion_ring: RefCell<IoCompletionRing>,
177}
178
179thread_local! {
180 static THREADPOOL_WORKER_STATE: LoanCell<AffinitizedWorkerState> = const { LoanCell::new() };
181}
182
183impl Wake for Worker {
184 fn wake_by_ref(self: &Arc<Self>) {
185 THREADPOOL_WORKER_STATE.with(|state| {
186 state.borrow(|state| {
187 if let Some(state) = state {
188 if Arc::ptr_eq(self, &state.worker) {
189 state.wake.set(true);
190 return;
191 }
192 }
193 unsafe {
197 self.io_ring.push(opcode::Nop::new().build(), true);
198 }
199 })
200 })
201 }
202
203 fn wake(self: Arc<Self>) {
204 self.wake_by_ref()
205 }
206}
207
208impl Worker {
209 pub fn new(io_ring_size: u32, io_ring: IoRing) -> Self {
210 Self {
211 io_ring_size,
212 io_ring,
213 }
214 }
215
216 pub fn initiator(self: Arc<Self>, scheduler: Scheduler) -> IoInitiator {
217 IoInitiator {
218 client: Arc::new(WorkerClient {
219 scheduler,
220 worker: self,
221 }),
222 }
223 }
224
225 pub fn run<Fut: Future>(
226 self: Arc<Self>,
227 completion_ring: IoCompletionRing,
228 fut: Fut,
229 ) -> Fut::Output {
230 tracing::debug!(
231 io_ring_size = self.io_ring_size,
232 "AffinitizedWorker running"
233 );
234
235 let waker = self.clone().into();
236 let mut cx = Context::from_waker(&waker);
237 let mut fut = pin!(fut);
238 let mut idle_task = None;
239 let state = AffinitizedWorkerState {
240 worker: self,
241 wake: Cell::new(false),
242 new_idle_task: Cell::new(None),
243 completion_ring: RefCell::new(completion_ring),
244 };
245
246 THREADPOOL_WORKER_STATE.with(|slot| {
247 slot.lend(&state, || {
248 loop {
249 state.completion_ring.borrow_mut().process();
251
252 match fut.poll_unpin(&mut cx) {
253 Poll::Ready(r) => {
254 tracing::debug!("AffinitizedWorker exiting");
255 break r;
256 }
257 Poll::Pending => {}
258 }
259
260 if !state.wake.take() {
261 if let Some(new_idle_task) = state.new_idle_task.take() {
262 idle_task = Some(new_idle_task(IdleControl {
263 inner: state.worker.clone(),
264 }));
265 tracing::debug!("new idle task");
266 }
267
268 if let Some(task) = &mut idle_task {
269 match task.poll_unpin(&mut cx) {
270 Poll::Ready(()) => {
271 tracing::debug!("idle task done");
272 idle_task = None;
273 }
274 Poll::Pending => {}
275 }
276
277 if state.wake.take() {
278 continue;
279 }
280 }
281
282 minircu::global().quiesce();
302
303 state.worker.io_ring.submit_and_wait();
304 }
305 }
306 })
307 })
308 }
309}
310
311#[derive(Debug)]
313pub struct IdleControl {
314 inner: Arc<Worker>,
315}
316
317impl IdleControl {
318 pub fn pre_block(&mut self) -> bool {
323 THREADPOOL_WORKER_STATE.with(|state| {
324 state.borrow(|state| {
325 let state = state.unwrap();
326 assert!(Arc::ptr_eq(&state.worker, &self.inner));
327
328 self.inner.io_ring.submit();
333 !state.wake.get() && state.completion_ring.borrow().is_empty()
336 })
337 })
338 }
339
340 pub fn ring_fd(&self) -> BorrowedFd<'_> {
344 self.inner.io_ring.as_fd()
345 }
346}
347
348impl Spawn for IoInitiator {
349 fn scheduler(&self, _metadata: &TaskMetadata) -> Arc<dyn Schedule> {
350 self.client.clone()
351 }
352}
353
354#[derive(Debug, inspect::Inspect)]
355struct WorkerClient {
356 #[inspect(skip)]
357 scheduler: Scheduler,
358 #[inspect(flatten)]
359 worker: Arc<Worker>,
360}
361
362impl Schedule for WorkerClient {
363 fn schedule(&self, runnable: Runnable) {
364 self.scheduler.schedule(runnable)
365 }
366
367 fn name(&self) -> Arc<str> {
368 self.scheduler.name()
369 }
370}
371
372#[derive(Debug, Clone)]
375pub struct IoInitiator {
376 client: Arc<WorkerClient>,
377}
378
379impl IoInitiator {
380 pub fn probe(&self, opcode: u8) -> bool {
382 self.client.worker.io_ring.probe(opcode)
383 }
384
385 pub async unsafe fn issue_io<T, F>(&self, mut io_mem: T, f: F) -> (io::Result<i32>, T)
407 where
408 T: 'static + Unpin,
409 F: FnOnce(&mut T) -> squeue::Entry,
410 {
411 struct AbortOnDrop;
415
416 impl Drop for AbortOnDrop {
417 fn drop(&mut self) {
418 eprintln!("io dropped in flight, may reference stack memory, aborting process");
419 abort();
420 }
421 }
422
423 let abort_on_drop = AbortOnDrop;
425
426 let result = poll_fn({
428 enum State<F> {
429 NotIssued(F),
430 Issued(usize),
431 Invalid,
432 }
433
434 let mut state = State::NotIssued(f);
435 let io_mem = &mut io_mem;
436 move |cx: &mut Context<'_>| {
437 match std::mem::replace(&mut state, State::Invalid) {
438 State::NotIssued(f) => {
439 state = State::Issued(unsafe {
441 self.submit_io((f)(io_mem), IoMemory::new(()), cx.waker().clone())
442 });
443
444 Poll::Pending
446 }
447 State::Issued(idx) => match self.poll_io(cx, idx) {
448 Poll::Ready((result, _)) => Poll::Ready(result),
449 Poll::Pending => {
450 state = State::Issued(idx);
451 Poll::Pending
452 }
453 },
454 State::Invalid => unreachable!(),
455 }
456 }
457 })
458 .await;
459
460 std::mem::forget(abort_on_drop);
462
463 let result = if result >= 0 {
464 Ok(result)
465 } else {
466 Err(io::Error::from_raw_os_error(-result))
467 };
468
469 (result, io_mem)
470 }
471
472 unsafe fn submit_io(&self, sqe: squeue::Entry, io_mem: IoMemory, waker: Waker) -> usize {
476 let needs_submit = THREADPOOL_WORKER_STATE.with(|state| {
479 state.borrow(|state| {
480 state.is_none_or(|state| !Arc::ptr_eq(&state.worker, &self.client.worker))
481 })
482 });
483
484 unsafe {
486 self.client
487 .worker
488 .io_ring
489 .new_io(sqe, io_mem, waker, needs_submit)
490 }
491 }
492
493 fn poll_io(&self, cx: &mut Context<'_>, idx: usize) -> Poll<(i32, IoMemory)> {
494 self.client.worker.io_ring.poll_io(cx, idx)
495 }
496
497 fn drop_io(&self, idx: usize) {
498 self.client.worker.io_ring.drop_io(idx);
499 }
500}
501
502pub struct Io<T, Init: Borrow<IoInitiator> = IoInitiator> {
504 initiator: Init,
505 state: IoState<T>,
506}
507
508enum IoState<T> {
509 NotStarted(squeue::Entry, T),
510 Started(usize),
511 Completed(i32, T),
512 Invalid,
513}
514
515impl<T, Init: Borrow<IoInitiator>> Debug for Io<T, Init> {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 f.pad("Io")
518 }
519}
520
521impl<T: 'static + Send + Sync + Unpin, Init: Borrow<IoInitiator> + Unpin> Io<T, Init> {
522 pub unsafe fn new(initiator: Init, submission_queue_entry: squeue::Entry, io_mem: T) -> Self {
530 Self {
531 initiator,
532 state: IoState::NotStarted(submission_queue_entry, io_mem),
533 }
534 }
535
536 pub fn initiator(&self) -> &Init {
538 &self.initiator
539 }
540
541 pub fn cancel(&self) {
543 unsafe {
545 self.cancel_inner(|user_data| opcode::AsyncCancel::new(user_data).build());
546 }
547 }
548
549 pub fn cancel_timeout(&self) {
551 unsafe {
553 self.cancel_inner(|user_data| opcode::TimeoutRemove::new(user_data).build());
554 }
555 }
556
557 pub fn cancel_poll(&self) {
559 unsafe {
561 self.cancel_inner(|user_data| opcode::PollRemove::new(user_data).build());
562 }
563 }
564
565 unsafe fn cancel_inner(&self, f: impl FnOnce(u64) -> squeue::Entry) {
569 let sqe = f(self.user_data().unwrap());
570 let idx = unsafe {
572 self.initiator
573 .borrow()
574 .submit_io(sqe, IoMemory::new(()), Waker::noop().clone())
575 };
576 self.initiator.borrow().drop_io(idx);
577 }
578
579 pub fn into_mem(mut self) -> T {
583 match std::mem::replace(&mut self.state, IoState::Invalid) {
584 IoState::Started(_) => {
585 panic!("io is not complete");
586 }
587 IoState::NotStarted(_, io_mem) | IoState::Completed(_, io_mem) => io_mem,
588 IoState::Invalid => unreachable!(),
589 }
590 }
591
592 pub fn user_data(&self) -> Option<u64> {
596 match self.state {
597 IoState::Started(idx) => Some(idx as u64),
598 IoState::NotStarted(_, _) | IoState::Completed(_, _) | IoState::Invalid => None,
599 }
600 }
601}
602
603impl<T: 'static + Sync + Send + Unpin, Init: Borrow<IoInitiator> + Unpin> Future for Io<T, Init> {
604 type Output = io::Result<i32>;
605
606 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
607 let this = Pin::get_mut(self);
608 let result = match std::mem::replace(&mut this.state, IoState::Invalid) {
609 IoState::NotStarted(entry, io_mem) => {
610 let idx = unsafe {
612 this.initiator.borrow().submit_io(
613 entry,
614 IoMemory::new(io_mem),
615 cx.waker().clone(),
616 )
617 };
618 this.state = IoState::Started(idx);
619 return Poll::Pending;
620 }
621 IoState::Started(idx) => {
622 this.state = IoState::Started(idx);
623 let (result, io_mem) = std::task::ready!(this.initiator.borrow().poll_io(cx, idx));
624 this.state = IoState::Completed(result, io_mem.downcast());
625 result
626 }
627 IoState::Completed(result, io_mem) => {
628 this.state = IoState::Completed(result, io_mem);
629 result
630 }
631 IoState::Invalid => unreachable!(),
632 };
633 let result = if result >= 0 {
634 Ok(result)
635 } else {
636 Err(io::Error::from_raw_os_error(-result))
637 };
638 Poll::Ready(result)
639 }
640}
641
642impl<T, Init: Borrow<IoInitiator>> Drop for Io<T, Init> {
643 fn drop(&mut self) {
644 if let IoState::Started(idx) = self.state {
645 self.initiator.borrow().drop_io(idx);
646 }
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 #![expect(
653 clippy::disallowed_methods,
654 reason = "test code using futures channels"
655 )]
656
657 use super::Io;
658 use super::IoRing;
659 use crate::IoUringPool;
660 use crate::uring::tests::SingleThreadPool;
661 use futures::executor::block_on;
662 use io_uring::opcode;
663 use io_uring::types;
664 use pal_async::task::Spawn;
665 use parking_lot::Mutex;
666 use std::future::Future;
667 use std::os::unix::prelude::*;
668 use std::pin::Pin;
669 use std::sync::Arc;
670 use std::sync::atomic::AtomicBool;
671 use std::sync::atomic::Ordering;
672 use std::task::Context;
673 use std::task::Poll;
674 use std::task::Waker;
675 use std::thread;
676 use std::time::Duration;
677 use std::time::Instant;
678 use tempfile::NamedTempFile;
679 use test_with_tracing::test;
680
681 const PAGE_SIZE: usize = 4096;
682
683 fn new_test_file() -> NamedTempFile {
684 let mut file = NamedTempFile::new().unwrap();
685 file.as_file_mut().set_len(1024 * 64).unwrap();
686 file
687 }
688
689 struct Env {
690 rx: std::sync::mpsc::Receiver<()>,
691 }
692
693 struct TestCase {
694 tp: SingleThreadPool,
695 file: NamedTempFile,
696 _tx: std::sync::mpsc::Sender<()>,
697 }
698
699 impl Drop for Env {
700 fn drop(&mut self) {
701 while self.rx.recv().is_ok() {}
702 }
703 }
704
705 fn new_test() -> (Env, TestCase) {
706 let file = new_test_file();
707 let tp = SingleThreadPool::new().unwrap();
708 let (tx, rx) = std::sync::mpsc::channel();
709 (Env { rx }, TestCase { tp, file, _tx: tx })
710 }
711
712 struct Timeout {
713 shared_state: Arc<Mutex<TimeoutState>>,
714 }
715
716 struct TimeoutState {
717 completed: bool,
718 waker: Option<Waker>,
719 }
720
721 impl Future for Timeout {
722 type Output = ();
723
724 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
725 let mut shared_state = self.shared_state.lock();
726 if shared_state.completed {
727 Poll::Ready(())
728 } else {
729 shared_state.waker = Some(cx.waker().clone());
730 Poll::Pending
731 }
732 }
733 }
734
735 impl Timeout {
736 fn new(duration: Duration) -> Self {
737 let shared_state = Arc::new(Mutex::new(TimeoutState {
738 completed: false,
739 waker: None,
740 }));
741
742 let thread_shared_state = shared_state.clone();
744 thread::spawn(move || {
745 thread::sleep(duration);
746 let mut shared_state = thread_shared_state.lock();
747 shared_state.completed = true;
748 if let Some(waker) = shared_state.waker.take() {
749 waker.wake()
750 }
751 });
752
753 Timeout { shared_state }
754 }
755 }
756
757 macro_rules! skip_if_no_io_uring_support {
760 () => {
761 if IoRing::new(1).is_err() {
762 println!("Test case skipped (no IO-Uring support)");
763 return;
764 }
765 };
766 }
767
768 #[test]
769 fn test_task_executor() {
770 skip_if_no_io_uring_support!();
771 let tp = SingleThreadPool::new().unwrap();
772
773 let (tx, rx) = std::sync::mpsc::channel();
774
775 tp.initiator()
776 .spawn("test", async move {
777 let now = Instant::now();
778 Timeout::new(Duration::from_secs(2)).await;
779 assert!(now.elapsed().as_secs() >= 2);
780 tx.send(()).unwrap();
781 })
782 .detach();
783
784 rx.recv().unwrap();
785 }
786
787 #[test]
788 fn test_local_task_executor() {
789 skip_if_no_io_uring_support!();
790 let tp = SingleThreadPool::new().unwrap();
791
792 let (tx, rx) = std::sync::mpsc::channel();
793
794 tp.initiator()
795 .spawn("test", async move {
796 let now = Instant::now();
797 Timeout::new(Duration::from_secs(2)).await;
798 assert!(now.elapsed().as_secs() >= 2);
799 tx.send(()).unwrap();
800 })
801 .detach();
802
803 rx.recv().unwrap();
804 }
805
806 #[test]
807 fn test_serial_io() {
808 skip_if_no_io_uring_support!();
809 let (_env, test) = new_test();
810
811 block_on(async move {
812 let _ = &test;
813 let mut write_buf = vec![0u8; PAGE_SIZE];
814 for (i, b) in write_buf.iter_mut().enumerate() {
815 *b = i as u8;
816 }
817
818 let sqe = opcode::Write::new(
819 types::Fd(test.file.as_fd().as_raw_fd()),
820 write_buf.as_ptr(),
821 write_buf.len() as _,
822 )
823 .offset(0)
824 .build();
825
826 let mut write_io = unsafe { Io::new(test.tp.initiator(), sqe, write_buf) };
828 (&mut write_io).await.unwrap();
829 let write_buf = write_io.into_mem();
830
831 let sqe = opcode::Fsync::new(types::Fd(test.file.as_fd().as_raw_fd())).build();
832 unsafe {
834 Io::new(test.tp.initiator(), sqe, ()).await.unwrap();
835 }
836
837 let mut read_buf = vec![0u8; PAGE_SIZE];
838 let sqe = opcode::Read::new(
839 types::Fd(test.file.as_fd().as_raw_fd()),
840 read_buf.as_mut_ptr(),
841 read_buf.len() as _,
842 )
843 .offset(0)
844 .build();
845
846 let mut read_io = unsafe { Io::new(test.tp.initiator(), sqe, read_buf) };
848 (&mut read_io).await.unwrap();
849 let read_buf = read_io.into_mem();
850
851 assert_eq!(&write_buf[..], &read_buf[..]);
852 });
853 }
854
855 #[test]
856 fn test_stack_io() {
857 skip_if_no_io_uring_support!();
858 let (_env, test) = new_test();
859
860 block_on(async move {
861 let _ = &test;
862 let mut write_buf = [0; 100];
863 for (i, b) in write_buf.iter_mut().enumerate() {
864 *b = i as u8;
865 }
866
867 let (r, write_buf) = unsafe {
869 test.tp
870 .initiator()
871 .issue_io(write_buf, |write_buf| {
872 opcode::Write::new(
873 types::Fd(test.file.as_fd().as_raw_fd()),
874 write_buf.as_ptr(),
875 write_buf.len() as _,
876 )
877 .offset(0)
878 .build()
879 })
880 .await
881 };
882 r.unwrap();
883
884 unsafe {
886 test.tp
887 .initiator()
888 .issue_io((), |_| {
889 opcode::Fsync::new(types::Fd(test.file.as_fd().as_raw_fd())).build()
890 })
891 .await
892 .0
893 .unwrap();
894 }
895
896 let read_buf = [0u8; 100];
897 let (r, read_buf) = unsafe {
899 test.tp
900 .initiator()
901 .issue_io(read_buf, |read_buf| {
902 opcode::Read::new(
903 types::Fd(test.file.as_fd().as_raw_fd()),
904 read_buf.as_mut_ptr(),
905 read_buf.len() as _,
906 )
907 .offset(0)
908 .build()
909 })
910 .await
911 };
912 r.unwrap();
913
914 assert_eq!(&write_buf[..], &read_buf[..]);
915 });
916 }
917
918 #[test]
924 #[cfg(not(feature = "ci"))]
925 fn test_split_io() {
926 skip_if_no_io_uring_support!();
927 let (_env, test) = new_test();
928
929 block_on(async move {
930 let _ = &test;
931 let mut write_buf1 = vec![0u8; PAGE_SIZE];
932 for (i, b) in write_buf1.iter_mut().enumerate() {
933 *b = i as u8;
934 }
935 let sqe1 = opcode::Write::new(
936 types::Fd(test.file.as_fd().as_raw_fd()),
937 write_buf1.as_mut_ptr(),
938 write_buf1.len() as _,
939 )
940 .offset(0)
941 .build();
942 let write1 = unsafe { Io::new(test.tp.initiator(), sqe1, write_buf1) };
944
945 let mut write_buf2 = vec![0u8; PAGE_SIZE];
946 for (i, b) in write_buf2.iter_mut().enumerate() {
947 *b = i as u8;
948 }
949 let sqe2 = opcode::Write::new(
950 types::Fd(test.file.as_fd().as_raw_fd()),
951 write_buf2.as_mut_ptr(),
952 write_buf2.len() as _,
953 )
954 .offset(4096)
955 .build();
956 let write2 = unsafe { Io::new(test.tp.initiator(), sqe2, write_buf2) };
958
959 let (r1, r2) = futures::join!(write1, write2);
960 r1.unwrap();
961 r2.unwrap();
962 });
963 }
964
965 #[test]
971 #[cfg(not(feature = "ci"))]
972 fn test_tp_io() {
973 skip_if_no_io_uring_support!();
974 let (_env, test) = new_test();
975
976 test.tp
977 .initiator()
978 .clone()
979 .spawn("test", async move {
980 let _ = &test;
981 let mut write_buf = vec![0u8; PAGE_SIZE];
982 for (i, b) in write_buf.iter_mut().enumerate() {
983 *b = i as u8;
984 }
985
986 let sqe = opcode::Write::new(
987 types::Fd(test.file.as_fd().as_raw_fd()),
988 write_buf.as_mut_ptr(),
989 write_buf.len() as _,
990 )
991 .offset(0)
992 .build();
993
994 let mut write_io = unsafe { Io::new(test.tp.initiator(), sqe, write_buf) };
996 (&mut write_io).await.unwrap();
997 let write_buf = write_io.into_mem();
998
999 let mut read_buf = vec![0u8; PAGE_SIZE];
1000
1001 let sqe = opcode::Read::new(
1002 types::Fd(test.file.as_fd().as_raw_fd()),
1003 read_buf.as_mut_ptr(),
1004 read_buf.len() as _,
1005 )
1006 .offset(0)
1007 .build();
1008
1009 let mut read_io = unsafe { Io::new(test.tp.initiator(), sqe, read_buf) };
1011 (&mut read_io).await.unwrap();
1012 let read_buf = read_io.into_mem();
1013
1014 assert_eq!(&write_buf[..], &read_buf[..]);
1015 })
1016 .detach();
1017 }
1018
1019 #[test]
1020 fn test_run_until_none() {
1021 skip_if_no_io_uring_support!();
1022 let (send, recv) = futures::channel::oneshot::channel();
1023 let (send2, recv2) = futures::channel::oneshot::channel();
1024 let pool = IoUringPool::new("test", 16).unwrap();
1025 let done = Arc::new(AtomicBool::new(false));
1026 pool.client()
1027 .initiator()
1028 .spawn("hmm", {
1029 async move {
1030 recv.await.unwrap();
1031 send2.send(()).unwrap();
1032 }
1033 })
1034 .detach();
1035 pool.client().set_idle_task({
1036 let done = done.clone();
1037 |_ctl| async move {
1038 send.send(()).unwrap();
1039 recv2.await.unwrap();
1040 done.store(true, Ordering::SeqCst);
1041 }
1042 });
1043 pool.run();
1044 assert!(done.load(Ordering::SeqCst));
1045 }
1046}