Tokio 0.3 发布及 1.0 版本的规划

2020 年 10 月 15 日

Tokio 团队很高兴地宣布 Tokio 0.3 的发布。此版本作为 Tokio 1.0 的 Beta 版。API 的粗糙之处已得到修复。此版本是在作为 1.0 版本的一部分进行稳定之前验证更改的机会。由于大多数问题都很小,我们预计从 0.2 升级到 0.3 将很容易。

1.0 版本计划

Tokio 团队计划在 2020 年 12 月底发布 Tokio 1.0。这个日期临近了。我们请求 *您*,Tokio 社区,试用 Tokio 0.3 并通过 GitHub Issues 或我们的 Discord 频道向我们提供反馈。一旦我们发布 Tokio 1.0,我们将承诺以下稳定性保证

  1. 至少 5 年的维护。
  2. 在假设的 2.0 版本发布前至少 3 年。

新特性?

Tokio 0.3 的主要变化是

  1. IO traits 的更改。
  2. 新的运行时构建器。
  3. I/O 驱动程序已全面改进
  4. API 具有面向未来的特性。

您可以在 changelog 上找到完整列表。

IO traits 的更改

在 @sfackler 的 RFC 的引导下,我们更改了 AsyncReadAsyncWrite traits 以支持读取未初始化的内存。此更改 *仅* 影响实现 AsyncReadAsyncWrite traits 或手动调用 poll_* 方法的用户。如果使用了 AsyncReadExtAsyncWriteExt traits,则无需更改。

以下是新的 AsyncRead 的简化版本

pub trait AsyncRead {
    fn poll_read(
        self: Pin<&mut Self>, 
        cx: &mut Context<'_>, 
        buf: &mut ReadBuf<'_>
    ) -> Poll<Result<()>>;
}

pub struct ReadBuf<'a> {
    buf: &'a mut [MaybeUninit<u8>],
    filled: usize,
    initialized: usize,
}

impl<'a> ReadBuf<'a> {
    // functions here, see RFC
}

Tokio 0.2 的 poll_read 方法的 &mut [u8] 参数存在一些意想不到的尖锐问题:AsyncRead trait 的实现者(而不是最终消费者)可以读取存储在 &mut [u8] slice 中的数据。如果程序员创建了一个引用未初始化内存的字节可变 slice(&mut [u8])(例如,vector 中的多余容量),那么读取 &mut [u8] 数据将导致未定义的行为。新设计通过提供一个跟踪未初始化内存的 ReadBuf 结构来缓解此问题,从而消除了初始化内存的需要。

此外,poll_read_bufpoll_write_buf 方法已从这两个 traits 中删除。实际上,这些方法很少被实现。向量化操作将直接在支持它们的类型上实现。

特性标志简化

我们减少了使用 Tokio 核心特性所需的特性标志数量。dnstcpudpuds 特性标志被折叠为一个 net 特性标志。我们还将 rt-corert-util 合并为一个 rt 特性标志。此 rt 特性标志现在包含执行 futures 所需的一切,除了多线程运行时,它现在位于 rt-multi-thread 特性标志下(在 0.2 中称为 rt-threaded)。

  • dns, tcp, udpuds -> net
  • rt-utilrt-core -> rt
  • rt-threaded -> rt-multi-thread

运行时和构建器重构

Tokio 团队改进了运行时 Builder,以在特性标志排列中提供更好的统一性,并更耐误用。为了实现这一点,我们在 Builder 类型中添加了两个新的构造函数,以支持构建 Tokio 运行时的两种变体。此外,Tokio 在使用 Runtime::new 时将不再根据特性标志选择运行时变体,而是始终使用多线程运行时,这仅在 rt-multi-thread 特性标志下可用。此外,我们将 core_threads 构建器方法重命名为更准确的 worker_threads

我们还重构了运行时模块。Tokio 0.2 公开了两种类型来与 Tokio 运行时交互:RuntimeHandleRuntime 有一个需要 &mut selfblock_on 方法,而 Handle 有一个需要 &selfblock_on 方法。在 Tokio 0.3 中,我们将 RuntimeHandle 折叠为一个 Runtime。新运行时上的 block_on 方法接受 &self,这意味着它可以毫无问题地放入 Arc 中。

配置具有 6 个 worker threads 的多线程运行时的示例

use tokio::runtime::Builder;

let rt = Builder::new_multi_thread()
    .worker_threads(6)
    .enable_all()
    .build()
    .unwrap();

rt.block_on(async {
    println!("Hello world!");
});

方法已更改为 &self

我们彻底修改了在 socket 类型中处理 wakers 的方式,以允许通过 &self 而不是 &mut self 进行并发调用。为了实现这一点,我们不得不重写内部处理 wakers 的方式。问题在于基于 poll_* 的方法的工作方式。在 futures 契约下,它声明 poll_* fn 应该只存储最后一次调用它的 waker。

请注意,在多次调用 poll 时,只有传递给最近调用的 Context 中的 Waker 才应计划接收唤醒。

来自 Future

这意味着这些 poll_* 方法不能并发调用或唤醒。为了使 &self 在我们的 socket 类型的 async fn 中工作,我们重构了我们的 io 驱动程序,以使用内部侵入式链表来存储 wakers。通过这样做,我们的 socket 类型在被调用时可以接受 &self 而不是 &mut self

移除非 1.0 版本 crate 和为 1.0 版本面向未来

为了推进 1.0 版本,我们从公共 API 中移除了许多非 1.0 版本的依赖项。这包括 bytes 和 mio。这将使我们能够推进面向未来的更稳定的 1.0 版本,同时我们继续在我们的底层 crates 中进行创新。

Mio 0.7

我们最终也将 mio 升级到 0.7,它最初于 2019 年 12 月以 alpha 版本发布,但没有赶上 Tokio 0.2。从那时起,mio 0.7 有时间成熟,并最终进入 Tokio 0.3 的这个版本。这升级了一些关键依赖项,例如 winapi 从 0.2 到 0.3。此外,mio 0.7 现在附带了 wepoll 的纯 Rust 实现,wepoll 是一个为基于 Windows 的应用程序实现 epoll API 的库。

0.2 和 0.3 版本之间的兼容性

可以通过 tokio-compat-02 crate 逐步升级到 Tokio 0.3。这个 crate 将从 Tokio 0.2 启动一个单线程后台运行时,并允许您在其上下文中包装任何 future。这将允许您在任何运行时(包括 Tokio 0.3)中运行需要 Tokio 0.2 的库。下面是如何使用 hyper 0.13 发送请求的示例,hyper 0.13 需要 Tokio 0.2。

use hyper::{Client, Uri};
use tokio_compat_02::FutureExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    // This will not panic because we are wrapping it in the
    // Tokio 0.2 context via the `FutureExt::compat` fn.
    client
        .get(Uri::from_static("https://tokio.rust-lang.net.cn"))
        .compat()
        .await?;

    Ok(())
}

结论

自 Tokio 0.2 发布以来,我们已经有超过 50 位贡献者帮助我们。我们非常感谢我们的社区帮助我们找到并修复错误。随着我们继续朝着 1.0 版本迈进,反馈将比以往任何时候都更加重要,因此请随时提出 issue 或加入我们的 Discord,以帮助我们实现目标。