1mod abi;
38use parking_lot::Mutex;
39use std::collections::{hash_map, HashMap};
40use std::os::unix::io::RawFd;
41use std::pin::Pin;
42use std::os::raw::c_long;
43use libc::time_t;
44use std::sync::{
45 atomic::{AtomicPtr, AtomicUsize, Ordering},
46 Arc,
47};
48
49const LIBAIO_EAGAIN: libc::c_int = -libc::EAGAIN;
50const LIBAIO_ENOMEM: libc::c_int = -libc::ENOMEM;
51const LIBAIO_ENOSYS: libc::c_int = -libc::ENOSYS;
52
53#[derive(Debug)]
54pub enum Error {
55 MaxEventsTooLarge,
56 LowKernelRes,
57 NotSupported,
58 OtherError,
59}
60
61struct AIOContext(abi::IOContextPtr);
63unsafe impl Sync for AIOContext {}
64unsafe impl Send for AIOContext {}
65
66impl std::ops::Deref for AIOContext {
67 type Target = abi::IOContextPtr;
68 fn deref(&self) -> &abi::IOContextPtr {
69 &self.0
70 }
71}
72
73impl AIOContext {
74 fn new(maxevents: u32) -> Result<Self, Error> {
75 let mut ctx = std::ptr::null_mut();
76 unsafe {
77 match abi::io_setup(maxevents as libc::c_int, &mut ctx) {
78 0 => Ok(()),
79 LIBAIO_EAGAIN => Err(Error::MaxEventsTooLarge),
80 LIBAIO_ENOMEM => Err(Error::LowKernelRes),
81 LIBAIO_ENOSYS => Err(Error::NotSupported),
82 _ => Err(Error::OtherError),
83 }
84 .and_then(|_| Ok(AIOContext(ctx)))
85 }
86 }
87}
88
89impl Drop for AIOContext {
90 fn drop(&mut self) {
91 unsafe {
92 assert_eq!(abi::io_destroy(self.0), 0);
93 }
94 }
95}
96
97pub struct AIO {
99 data: Option<Box<[u8]>>,
101 iocb: AtomicPtr<abi::IOCb>,
102 id: u64,
103}
104
105impl AIO {
106 fn new(
107 id: u64,
108 fd: RawFd,
109 off: u64,
110 data: Box<[u8]>,
111 priority: u16,
112 flags: u32,
113 opcode: abi::IOCmd,
114 ) -> Self {
115 let mut iocb = Box::new(abi::IOCb::default());
116 iocb.aio_fildes = fd as u32;
117 iocb.aio_lio_opcode = opcode as u16;
118 iocb.aio_reqprio = priority;
119 iocb.aio_buf = data.as_ptr() as u64;
120 iocb.aio_nbytes = data.len() as u64;
121 iocb.aio_offset = off;
122 iocb.aio_flags = flags;
123 iocb.aio_data = id;
124 let iocb = AtomicPtr::new(Box::into_raw(iocb));
125 let data = Some(data);
126 AIO { iocb, id, data }
127 }
128}
129
130impl Drop for AIO {
131 fn drop(&mut self) {
132 unsafe {
133 drop(Box::from_raw(self.iocb.load(Ordering::Acquire)));
134 }
135 }
136}
137
138pub type AIOResult = (Result<usize, i32>, Box<[u8]>);
141
142pub struct AIOFuture {
145 notifier: Arc<AIONotifier>,
146 aio_id: u64,
147}
148
149impl AIOFuture {
150 pub fn get_id(&self) -> u64 {
151 self.aio_id
152 }
153}
154
155impl std::future::Future for AIOFuture {
156 type Output = AIOResult;
157 fn poll(
158 self: Pin<&mut Self>,
159 cx: &mut std::task::Context,
160 ) -> std::task::Poll<Self::Output> {
161 if let Some(ret) = self.notifier.poll(self.aio_id, cx.waker()) {
162 std::task::Poll::Ready(ret)
163 } else {
164 std::task::Poll::Pending
165 }
166 }
167}
168
169impl Drop for AIOFuture {
170 fn drop(&mut self) {
171 self.notifier.dropped(self.aio_id)
172 }
173}
174
175enum AIOState {
176 FutureInit(AIO, bool),
177 FuturePending(AIO, std::task::Waker, bool),
178 FutureDone(AIOResult),
179}
180
181pub struct AIONotifier {
183 waiting: Mutex<HashMap<u64, AIOState>>,
184 npending: AtomicUsize,
185 io_ctx: AIOContext,
186 #[cfg(feature = "emulated-failure")]
187 emul_fail: Option<EmulatedFailureShared>,
188}
189
190impl AIONotifier {
191 fn register_notify(&self, id: u64, state: AIOState) {
192 let mut waiting = self.waiting.lock();
193 assert!(waiting.insert(id, state).is_none());
194 }
195
196 fn dropped(&self, id: u64) {
197 let mut waiting = self.waiting.lock();
198 match waiting.entry(id) {
199 hash_map::Entry::Occupied(mut e) => match e.get_mut() {
200 AIOState::FutureInit(_, dropped) => *dropped = true,
201 AIOState::FuturePending(_, _, dropped) => *dropped = true,
202 AIOState::FutureDone(_) => {
203 e.remove();
204 }
205 },
206 _ => (),
207 }
208 }
209
210 fn poll(&self, id: u64, waker: &std::task::Waker) -> Option<AIOResult> {
211 let mut waiting = self.waiting.lock();
212 match waiting.entry(id) {
213 hash_map::Entry::Occupied(e) => {
214 let v = e.remove();
215 match v {
216 AIOState::FutureInit(aio, _) => {
217 waiting.insert(
218 id,
219 AIOState::FuturePending(aio, waker.clone(), false),
220 );
221 None
222 }
223 AIOState::FuturePending(aio, waker, dropped) => {
224 waiting.insert(
225 id,
226 AIOState::FuturePending(aio, waker, dropped),
227 );
228 None
229 }
230 AIOState::FutureDone(res) => Some(res),
231 }
232 }
233 _ => unreachable!(),
234 }
235 }
236
237 fn finish(&self, id: u64, res: i64) {
238 let mut w = self.waiting.lock();
239 self.npending.fetch_sub(1, Ordering::Relaxed);
240 match w.entry(id) {
241 hash_map::Entry::Occupied(e) => match e.remove() {
242 AIOState::FutureInit(mut aio, dropped) => {
243 if !dropped {
244 let data = aio.data.take().unwrap();
245 w.insert(
246 id,
247 AIOState::FutureDone(if res >= 0 {
248 (Ok(res as usize), data)
249 } else {
250 (Err(-res as i32), data)
251 }),
252 );
253 }
254 }
255 AIOState::FuturePending(mut aio, waker, dropped) => {
256 if !dropped {
257 let data = aio.data.take().unwrap();
258 w.insert(
259 id,
260 AIOState::FutureDone(if res >= 0 {
261 (Ok(res as usize), data)
262 } else {
263 (Err(-res as i32), data)
264 }),
265 );
266 waker.wake();
267 }
268 }
269 AIOState::FutureDone(ret) => {
270 w.insert(id, AIOState::FutureDone(ret));
271 }
272 },
273 _ => unreachable!(),
274 }
275 }
276}
277
278pub struct AIOBuilder {
279 max_events: u32,
280 max_nwait: u16,
281 max_nbatched: usize,
282 timeout: Option<u32>,
283 #[cfg(feature = "emulated-failure")]
284 emul_fail: Option<EmulatedFailureShared>,
285}
286
287impl Default for AIOBuilder {
288 fn default() -> Self {
289 AIOBuilder {
290 max_events: 128,
291 max_nwait: 128,
292 max_nbatched: 128,
293 timeout: None,
294 #[cfg(feature = "emulated-failure")]
295 emul_fail: None,
296 }
297 }
298}
299
300impl AIOBuilder {
301 pub fn max_events(&mut self, v: u32) -> &mut Self {
303 self.max_events = v;
304 self
305 }
306
307 pub fn max_nwait(&mut self, v: u16) -> &mut Self {
309 self.max_nwait = v;
310 self
311 }
312
313 pub fn max_nbatched(&mut self, v: usize) -> &mut Self {
315 self.max_nbatched = v;
316 self
317 }
318
319 pub fn timeout(&mut self, sec: u32) -> &mut Self {
321 self.timeout = Some(sec);
322 self
323 }
324
325 #[cfg(feature = "emulated-failure")]
326 pub fn emulated_failure(&mut self, ef: EmulatedFailureShared) -> &mut Self {
327 self.emul_fail = Some(ef);
328 self
329 }
330
331 pub fn build(&mut self) -> Result<AIOManager, Error> {
334 let (scheduler_in, scheduler_out) =
335 new_batch_scheduler(self.max_nbatched);
336 let (exit_s, exit_r) = crossbeam_channel::bounded(0);
337
338 let notifier = Arc::new(AIONotifier {
339 io_ctx: AIOContext::new(self.max_events)?,
340 waiting: Mutex::new(HashMap::new()),
341 npending: AtomicUsize::new(0),
342 #[cfg(feature = "emulated-failure")]
343 emul_fail: self.emul_fail.as_ref().map(|ef| ef.clone()),
344 });
345 let mut aiomgr = AIOManager {
346 notifier,
347 listener: None,
348 scheduler_in,
349 exit_s,
350 };
351 aiomgr.start(scheduler_out, exit_r, self.max_nwait, self.timeout)?;
352 Ok(aiomgr)
353 }
354}
355
356pub trait EmulatedFailure: Send {
357 fn tick(&mut self) -> Option<i64>;
358}
359
360pub type EmulatedFailureShared = Arc<Mutex<dyn EmulatedFailure>>;
361
362pub struct AIOManager {
364 notifier: Arc<AIONotifier>,
365 scheduler_in: AIOBatchSchedulerIn,
366 listener: Option<std::thread::JoinHandle<()>>,
367 exit_s: crossbeam_channel::Sender<()>,
368}
369
370impl AIOManager {
371 fn start(
372 &mut self,
373 mut scheduler_out: AIOBatchSchedulerOut,
374 exit_r: crossbeam_channel::Receiver<()>,
375 max_nwait: u16,
376 timeout: Option<u32>,
377 ) -> Result<(), Error> {
378 let n = self.notifier.clone();
379 self.listener = Some(std::thread::spawn(move || {
380 let mut timespec = timeout.and_then(|sec: u32| {
381 Some(libc::timespec {
382 tv_sec: sec as time_t,
383 tv_nsec: 0,
384 })
385 });
386 let mut ongoing = 0;
387 loop {
388 if ongoing == 0 && scheduler_out.is_empty() {
390 let mut sel = crossbeam_channel::Select::new();
391 sel.recv(&exit_r);
392 sel.recv(&scheduler_out.get_receiver());
393 if sel.ready() == 0 {
394 exit_r.recv().unwrap();
395 break
396 }
397 }
398 loop {
400 let nacc = scheduler_out.submit(&n);
401 ongoing += nacc;
402 if nacc == 0 {
403 break
404 }
405 }
406 if ongoing == 0 {
408 continue
409 }
410 let mut events =
412 vec![abi::IOEvent::default(); max_nwait as usize];
413 let ret = unsafe {
414 abi::io_getevents(
415 *n.io_ctx,
416 1,
417 max_nwait as c_long,
418 events.as_mut_ptr(),
419 timespec
420 .as_mut()
421 .and_then(|t| Some(t as *mut libc::timespec))
422 .unwrap_or(std::ptr::null_mut()),
423 )
424 };
425 if ret == 0 {
428 continue
429 }
430 assert!(ret > 0);
431 ongoing -= ret as usize;
432 for ev in events[..ret as usize].iter() {
433 #[cfg(not(feature = "emulated-failure"))]
434 n.finish(ev.data as u64, ev.res);
435 #[cfg(feature = "emulated-failure")]
436 {
437 let mut res = ev.res;
438 if let Some(emul_fail) = n.emul_fail.as_ref() {
439 let mut ef = emul_fail.lock();
440 if let Some(e) = ef.tick() {
441 res = e
442 }
443 }
444 n.finish(ev.data as u64, res);
445 }
446 }
447 }
448 }));
449 Ok(())
450 }
451
452 pub fn read(
453 &self,
454 fd: RawFd,
455 offset: u64,
456 length: usize,
457 priority: Option<u16>,
458 ) -> AIOFuture {
459 let priority = priority.unwrap_or(0);
460 let mut data = Vec::new();
461 data.resize(length, 0);
462 let data = data.into_boxed_slice();
463 let aio = AIO::new(
464 self.scheduler_in.next_id(),
465 fd,
466 offset,
467 data,
468 priority,
469 0,
470 abi::IOCmd::PRead,
471 );
472 self.scheduler_in.schedule(aio, &self.notifier)
473 }
474
475 pub fn write(
476 &self,
477 fd: RawFd,
478 offset: u64,
479 data: Box<[u8]>,
480 priority: Option<u16>,
481 ) -> AIOFuture {
482 let priority = priority.unwrap_or(0);
483 let aio = AIO::new(
484 self.scheduler_in.next_id(),
485 fd,
486 offset,
487 data,
488 priority,
489 0,
490 abi::IOCmd::PWrite,
491 );
492 self.scheduler_in.schedule(aio, &self.notifier)
493 }
494
495 pub fn copy_data(&self, aio_id: u64) -> Option<Vec<u8>> {
497 let w = self.notifier.waiting.lock();
498 w.get(&aio_id).and_then(|state| {
499 Some(
500 match state {
501 AIOState::FutureInit(aio, _) => {
502 &**aio.data.as_ref().unwrap()
503 }
504 AIOState::FuturePending(aio, _, _) => {
505 &**aio.data.as_ref().unwrap()
506 }
507 AIOState::FutureDone(res) => &res.1,
508 }
509 .to_vec(),
510 )
511 })
512 }
513
514 pub fn get_npending(&self) -> usize {
516 self.notifier.npending.load(Ordering::Relaxed)
517 }
518}
519
520impl Drop for AIOManager {
521 fn drop(&mut self) {
522 self.exit_s.send(()).unwrap();
523 self.listener.take().unwrap().join().unwrap();
524 }
525}
526
527pub struct AIOBatchSchedulerIn {
528 queue_in: crossbeam_channel::Sender<AtomicPtr<abi::IOCb>>,
529 last_id: std::cell::Cell<u64>,
530}
531
532pub struct AIOBatchSchedulerOut {
533 queue_out: crossbeam_channel::Receiver<AtomicPtr<abi::IOCb>>,
534 max_nbatched: usize,
535 leftover: Vec<AtomicPtr<abi::IOCb>>,
536}
537
538impl AIOBatchSchedulerIn {
539 fn schedule(&self, aio: AIO, notifier: &Arc<AIONotifier>) -> AIOFuture {
540 let fut = AIOFuture {
541 notifier: notifier.clone(),
542 aio_id: aio.id,
543 };
544 let iocb = aio.iocb.load(Ordering::Acquire);
545 notifier.register_notify(aio.id, AIOState::FutureInit(aio, false));
546 self.queue_in.send(AtomicPtr::new(iocb)).unwrap();
547 notifier.npending.fetch_add(1, Ordering::Relaxed);
548 fut
549 }
550
551 fn next_id(&self) -> u64 {
552 let id = self.last_id.get();
553 self.last_id.set(id.wrapping_add(1));
554 id
555 }
556}
557
558impl AIOBatchSchedulerOut {
559 fn get_receiver(
560 &self,
561 ) -> &crossbeam_channel::Receiver<AtomicPtr<abi::IOCb>> {
562 &self.queue_out
563 }
564 fn is_empty(&self) -> bool {
565 self.leftover.len() == 0
566 }
567 fn submit(&mut self, notifier: &AIONotifier) -> usize {
568 let mut quota = self.max_nbatched;
569 let mut pending = self
570 .leftover
571 .iter()
572 .map(|p| p.load(Ordering::Acquire))
573 .collect::<Vec<_>>();
574 if pending.len() < quota {
575 quota -= pending.len();
576 while let Ok(iocb) = self.queue_out.try_recv() {
577 pending.push(iocb.load(Ordering::Acquire));
578 quota -= 1;
579 if quota == 0 {
580 break
581 }
582 }
583 }
584 if pending.len() == 0 {
585 return 0
586 }
587 let mut ret = unsafe {
588 abi::io_submit(
589 *notifier.io_ctx,
590 pending.len() as c_long,
591 (&mut pending).as_mut_ptr(),
592 )
593 };
594 if ret < 0 && ret == LIBAIO_EAGAIN {
595 ret = 0
596 }
597 let nacc = ret as usize;
598 self.leftover = (&pending[nacc..])
599 .iter()
600 .map(|p| AtomicPtr::new(*p))
601 .collect::<Vec<_>>();
602 nacc
603 }
604}
605
606fn new_batch_scheduler(
608 max_nbatched: usize,
609) -> (AIOBatchSchedulerIn, AIOBatchSchedulerOut) {
610 let (queue_in, queue_out) = crossbeam_channel::unbounded();
611 let bin = AIOBatchSchedulerIn {
612 queue_in,
613 last_id: std::cell::Cell::new(0),
614 };
615 let bout = AIOBatchSchedulerOut {
616 queue_out,
617 max_nbatched,
618 leftover: Vec::new(),
619 };
620 (bin, bout)
621}