monoio 设计范式

monoio 是字节跳动开源的一个Rust异步运行时,与其它运行时最大的区别在于设计了一种每个核绑定一个线程,异步任务只在线程内执行,而不会在多线程之间传递。官方解释这种设计的好处在于,异步任务不需要在线程间传递,则不必设计为 Send + Sync,减轻了程序编写的负担。由于异步运行时多在重 I/O、轻计算的情况下发挥优势,这种情况下多线程的优势并不一定大于单线程。

main

在 h2_server example 中,main 函数是这样的

#[monoio::main(threads = 1)]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:5928").unwrap();
    println!("listening on {:?}", listener.local_addr());

    loop {
        if let Ok((socket, _peer_addr)) = listener.accept().await {
            monoio::spawn(async move {
                if let Err(e) = serve(socket).await {
                    println!("  -> err={e:?}");
                }
            });
        }
    }
}

将过程宏展开之后为

fn main() {
    let body = async {
        let listener = TcpListener::bind("127.0.0.1:5928").unwrap();
        loop {
            if let Ok((socket, _peer_addr)) = listener.accept().await {
                monoio::spawn(async move {
                    if let Err(e) = serve(socket).await {
                        {
                            ::std::io::_print(format_args!("  -> err={0:?}\n", e));
                        };
                    }
                });
            }
        }
    };
    #[allow(clippy::expect_used)]
    monoio::RuntimeBuilder::<monoio::FusionDriver>::new()
        .build()
        .expect("Failed building the Runtime")
        .block_on(body)
}
spawn
pub fn spawn<T>(future: T) -> JoinHandle<T::Output>
where
    T: Future + 'static,
    T::Output: 'static,
{
    // 将 future 转换为一个 task 和 一个 join handle
    let (task, join) = new_task(
        crate::utils::thread_id::get_current_thread_id(),
        future,
        LocalScheduler,
    );
  	// task 暂存,之后再处理
    CURRENT.with(|ctx| {
        ctx.tasks.push(task);
    });
  	// join handle 返回给用户
    join
}
Submiting a task

​ monoio 将每个异步 IO 操作表示为一个 Op

pub(crate) struct Op<T: 'static + OpAble> {
    // Driver running the operation
    pub(super) driver: driver::Inner,
    // Operation index in the slab(useless for legacy)
    pub(super) index: usize,
    // Per-operation data
    pub(super) data: Option<T>,
}

使用一个泛型 T 存储具体操作相关的数据,Op<T> 相当于一个 factory,所有的 Op 都有一个 submit_with 方法

impl<T: OpAble> Op<T> {
    /// Submit an operation to uring.
    pub(super) fn submit_with(data: T) -> io::Result<Op<T>> {
        driver::CURRENT.with(|this| this.submit_with(data))
    }
}

这个操作同时将一个 Op 操作提交到当前线程,并返回一个具体的 Op 实例。其中 driver::CURRENT 是一个加了一层封装的 thread local key,可以用于将当前的 thread local 变量设置为某个值后执行一个函数,然后再修改回原值。

driver::CURENT 就是一个 thread local 变量,其类型为 Inner,可以表示两种不同的 driver,这里只看 UringDriverUringDriversubmit_with_data 中将 data 封装为 io uring submit queue entry 之后提交到「提交队列」中,然后返回一个 Op, Op 的创建方式为

let mut op = Self::new_op(data, inner, Inner::Uring(this.clone())); // new_op 只是简单将 Op<T> 的各个字段赋值

Op<T> 既然是最终返回的类型,说明它同时也可以被 poll,观察 Op<T> 如何实现 Future trait

impl<T> Future for Op<T>
where
    T: Unpin + OpAble + 'static,
{
    type Output = Completion<T>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let me = &mut *self;
        let data_mut = me.data.as_mut().expect("unexpected operation state");
        // ready! 只是一个辅助展开 poll_op 结果的宏
      	// 如果poll_op返回 Pending 则同样返回 Pending
        let meta = ready!(me.driver.poll_op::<T>(data_mut, me.index, cx));
        me.index = usize::MAX;
        let data = me.data.take().expect("unexpected operation state");
        Poll::Ready(Completion { data, meta })
    }
}

主要看 me.driver.poll_op 发生了什么,io-uring driver 的实现如下

pub(crate) fn poll_op(
    this: &Rc<UnsafeCell<UringInner>>,
    index: usize,
    cx: &mut Context<'_>,
) -> Poll<CompletionMeta> {
    let inner = unsafe { &mut *this.get() };
    let lifecycle = unsafe { inner.ops.slab.get(index).unwrap_unchecked() };
    lifecycle.poll_op(cx)
}

inner.ops.slab 是存储所有 op 的容器,通过 index 获取到对应的 lifecycle,lifecycle 即一个异步任务的生命周期(提交 -> 等待 -> 完成)。

pub(crate) fn poll_op(mut self, cx: &mut Context<'_>) -> Poll<CompletionMeta> {
    let ref_mut = &mut self.lifecycle;
    match ref_mut {
        Lifecycle::Submitted => {
            *ref_mut = Lifecycle::Waiting(cx.waker().clone());
            return Poll::Pending;
        }
        Lifecycle::Waiting(waker) => {
            if !waker.will_wake(cx.waker()) {
                *ref_mut = Lifecycle::Waiting(cx.waker().clone());
            }
            return Poll::Pending;
        }
        _ => {}
    }
    match self.remove().lifecycle {
        Lifecycle::Completed(result, flags) => Poll::Ready(CompletionMeta { result, flags }),
        _ => unsafe { std::hint::unreachable_unchecked() },
    }
}

poll_op 主要检查 op 对应的 lifecycle 是否已经进入了完成状态,是的话将其从容器中删除,并返回结果,否则继续等待。

How a task is completed

​ 在上面的流程中完全没有涉及到一个异步任务是如何被完成的,要探究这一点,需要回到一个测试用例,上面讲到一个过程宏main函数在展开后最终回调用 Runtime::block_on 这个函数,这个函数内部会进入一个双重 loop

loop {
    loop {
        // Consume all tasks(with max round to prevent io starvation)
        let mut max_round = self.context.tasks.len() * 2;
        while let Some(t) = self.context.tasks.pop() {
            t.run();
            if max_round == 0 {
                // maybe there's a looping task
                break;
            } else {
                max_round -= 1;
            }
        }
        // Check main future
        while should_poll() {
            // check if ready
            if let std::task::Poll::Ready(t) = join.as_mut().poll(cx) {
                return t;
            }
        }
        if self.context.tasks.is_empty() {
            // No task to execute, we should wait for io blockingly
            // Hot path
            break;
        }
        // Cold path
        let _ = self.driver.submit();
    }

    // Wait and Process CQ(the error is ignored for not debug mode)
    #[cfg(not(all(debug_assertions, feature = "debug")))]
    let _ = self.driver.park();

    #[cfg(all(debug_assertions, feature = "debug"))]
    if let Err(e) = self.driver.park() {
        trace!("park error: {:?}", e);
    }
}

这个循环中包含的信息很多,先看一个 task 是如何运行的。

Harness

task.run() 最终会调用下面的 poll 函数

unsafe fn poll<T: Future, S: Schedule>(ptr: NonNull<Header>) {
    let harness = Harness::<T, S>::from_raw(ptr);
    harness.poll();
}

这里出现了一个新类型 Harness

// core.rs
#[repr(C)]
pub(crate) struct Cell<T: Future, S> {
    pub(crate) header: Header,
    pub(crate) core: Core<T, S>,
    pub(crate) trailer: Trailer,
}

pub(crate) struct Core<T: Future, S> {
    /// Scheduler used to drive this future
    pub(crate) scheduler: S,
    /// Either the future or the output
    pub(crate) stage: CoreStage<T>,
}
pub(crate) struct CoreStage<T: Future> {
    stage: UnsafeCell<Stage<T>>,
}
// 表示一个 Future 任务的运行阶段
pub(crate) enum Stage<T: Future> {
    Running(T),
    Finished(T::Output),
    Consumed,
}
#[repr(C)]
pub(crate) struct Header {
  	/// 表示一个任务的不同状态,如Running, Notified, Idle等
    /// 通过usize的不同bit表示一个 State,因此可以表示混合状态
    pub(crate) state: State,
    /// Table of function pointers for executing actions on the task.
    pub(crate) vtable: &'static Vtable,
    /// Thread ID(sync: used for wake task on its thread; sync disabled: do checking)
    pub(crate) owner_id: usize,
}

pub(crate) struct Trailer {
    /// Consumer task waiting on completion of this task.
    pub(crate) waker: UnsafeCell<Option<Waker>>,
}

// harness.rs
pub(crate) struct Harness<T: Future, S: 'static> {
    cell: NonNull<Cell<T, S>>,
}

来看一下 harness.poll() 发生了什么

impl<T, S> Harness<T, S>
where
    T: Future,
    S: Schedule,
{
    /// Polls the inner future.
    pub(super) fn poll(self) {
        trace!("MONOIO DEBUG[Harness]:: poll");
        match self.poll_inner() {
            PollFuture::Notified => {
                // We should re-schedule the task.
                self.header().state.ref_inc();
              	// 由于有其它任务 poll,所以需要再次进行 schedule。
                self.core().scheduler.yield_now(self.get_new_task());
            }
            PollFuture::Complete => {
                self.complete();
            }
            PollFuture::Done => (),
        }
    }

    /// 将 header 的 state 转换到 running,在poll一次之后
 		/// 根据执行状态返回 PollFuture 的不同值
  	/// PollFuture::Complete => 任务执行完成
  	/// PollFuture::Done => 任务未执行完成,但是没有其它任务poll它,所以暂时不需要再次 schedule
  	/// PollFuture::Notified => 任务未执行完成,但是有其它任务poll过,所以需要再次 schedule。
    fn poll_inner(&self) -> PollFuture {
        // notified -> running
        self.header().state.transition_to_running();
        let waker_ref = waker_ref::<T, S>(self.header());
        let cx = Context::from_waker(&waker_ref);
        // check if the stage is Stage::Running(future), if so,
        // run future.poll(cx), if the future finish, store output 
        // and set stage as Stage::Finished(output).
        let res = poll_future(&self.core().stage, cx);

        if res == Poll::Ready(()) {
            return PollFuture::Complete;
        }

        use super::state::TransitionToIdle;
      	// 取消 task 的 running 状态
        match self.header().state.transition_to_idle() {
            TransitionToIdle::Ok => PollFuture::Done,
            TransitionToIdle::OkNotified => PollFuture::Notified,
        }
    }
}

由于 monoio 是与每条线程绑定的,所以只有一种 local scheduler

impl Schedule for LocalScheduler {
  	// schedule 一个 task 等同于将其加入 task queue 中
    fn schedule(&self, task: Task<Self>) {
        crate::runtime::CURRENT.with(|cx| cx.tasks.push(task));
    }

    fn yield_now(&self, task: Task<Self>) {
        crate::runtime::CURRENT.with(|cx| cx.tasks.push_front(task));
    }
}

再看 RawWakerTable 是如何实现的

pub(super) fn raw_waker<T, S>(header: *const Header) -> RawWaker
where
    T: Future,
    S: Schedule,
{
    let ptr = header as *const ();
    let vtable = &RawWakerVTable::new(
        clone_waker::<T, S>,
        wake_by_val::<T, S>,
        wake_by_ref::<T, S>,
        drop_waker::<T, S>,
    );
    RawWaker::new(ptr, vtable)
}

wake_by_ref 为例,

unsafe fn wake_by_ref<T, S>(ptr: *const ())
where
    T: Future,
    S: Schedule,
{
    let ptr = NonNull::new_unchecked(ptr as *mut Header);
    let harness = Harness::<T, S>::from_raw(ptr);
    harness.wake_by_ref();
}

harness.wake_by_ref()

// haraness.rs
use super::state::TransitionToNotified;
match self.header().state.transition_to_notified() {
  	// 如果 task 需要提交,调用 scheduler 进行调度。
    TransitionToNotified::Submit => {
        // # Ref Count: +1 -> task
        self.header().state.ref_inc();
        self.core().scheduler.schedule(self.get_new_task());
    }
    TransitionToNotified::DoNothing => (),
}

// state.rs
// 如果一个 task 被 poll,则调用该函数
pub(super) fn transition_to_notified(&self) -> TransitionToNotified {
    self.fetch_update_action(|mut curr| {
      	// 如果当前正在运行,则不需要提交,但是将状态设为 notified
        let action = if curr.is_running() {
            curr.set_notified();
            TransitionToNotified::DoNothing
        } 
      	// 如果已经设置,则无事发生
      	else if curr.is_complete() || curr.is_notified() {
            TransitionToNotified::DoNothing
        } 
      	// 否则需要设置notified,并进行提交
      	else {
            curr.set_notified();
            TransitionToNotified::Submit
        };
        (action, Some(curr))
    })
}
How to wait for I/O events

​ 对于一个 I/O 密集的异步调用链,最终会进入一个等待 I/O 事件的状态,若 task queue 中的所有任务已经执行完成,则在 Runtime::block_on 函数中,会退出里层循环

loop {
  loop {
    ...
    if self.context.tasks.is_empty() {
      // 任务执行完成,里层循环退出
      break;
    }
    self.driver.park();
  }
}

当所有的任务执行完成后,最外层 future 并没有执行完成,则说明还有任务还在等待某个 I/O 事件,此时 driver 需要征询 I/O 事件库(不管是 io-uring 还是 epoll),检查是否有到达的 I/O 事件。

​ 如果有 I/O 事件到达,则每个事件会绑定到一个特定的 token,通过 token 可以在 driver 中获取对应的 waker,从而唤醒对应的 task。以 epoll 驱动的 driver 为例,在 inner_park 中(driver.park 会调用该函数)

let events = unsafe { &mut (*self.inner.get()).events };
// 调用 mio::epoll 库的函数,在 timeout 內到达的事件存入 events 中
match inner.poll.poll(events, timeout) {
    Ok(_) => {}
    Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
    Err(e) => return Err(e),
}
#[cfg(unix)]
let iter = events.iter();
#[cfg(windows)]
let iter = events.events.iter();
// 迭代所有 event,获取对应的 token
for event in iter {
    let token = event.token();
    #[cfg(feature = "sync")]
    if token != TOKEN_WAKEUP {
        inner.dispatch(token, Ready::from_mio(event));
    }
    #[cfg(not(feature = "sync"))]
    inner.dispatch(token, Ready::from_mio(event));
}

inner.dispatch 的实现为

impl LegacyInner {
  fn dispatch(&mut self, token: mio::Token, ready: Ready) {
      let mut sio = match self.io_dispatch.get(token.0) {
          Some(io) => io,
          None => {
              return;
          }
      };
      let ref_mut = sio.as_mut();
      ref_mut.set_readiness(|curr| curr | ready);
      ref_mut.wake(ready);
  }
}

这里的 sioScheduledIO 类型,其 wake 实现如下

pub(crate) fn wake(&mut self, ready: Ready) {
    if ready.is_readable() {
        if let Some(waker) = self.reader.take() {
            waker.wake();
        }
    }
    if ready.is_writable() {
        if let Some(waker) = self.writer.take() {
            waker.wake();
        }
    }
}

这里的 waker.wake() 就是实际调用虚函数表中的 wake 了,之前已经介绍过这个函数在 schduler 是 LocalScheduler 的情况下,会将 task 加入任务队列中,因此整个逻辑完成闭环。

posted @ 2025-05-02 12:57  kaleidopink  阅读(74)  评论(0)    收藏  举报