Mio 0.7-alpha.1 发布

2019 年 12 月 17 日

我们很高兴地宣布 Thomas de Zeeuw 成为 Mio 的新负责人。Mio 是 Tokio 和其他 Rust 项目背后的底层 I/O 可移植性抽象层。Thomas 一直是 Mio 0.7 版本的大部分工作背后的功臣,并将继续领导该 crate 达到 1.0 版本。以下是他的公告的其余部分。


Mio 0.7 是大约半年时间内 多位贡献者 共同努力的成果。与 Mio 0.6 版本相比,0.7 版本减少了提供的 API 的大小,旨在简化实现和使用。API 0.7 版本将接近未来 1.0 版本的提议 API。该 crate 的范围已缩小为提供跨平台事件通知机制和常用类型,例如跨线程轮询唤醒和非阻塞网络 I/O 原语。

主要变更

由于这是一个大型版本发布,这里仅描述了一些亮点,所有更改都可以在变更日志中找到。总体而言,为了降低实现的复杂性并尽可能消除开销,进行了大量的 API 更改。

封装原生操作系统类型

在 0.6 版本中,Mio 定义了自己的 Event 结构,原生操作系统类型会被转换为该结构。这意味着在轮询之后,原生类型(即 keventepoll_event 结构)将被转换为 Mio 的 Event 类型。在 0.7 版本中,Event 已更改为只是 keventepoll_event 的类型包装器,它提供了便捷的方法来获取事件就绪指示器(例如可读或可写)。整个 crate 中都进行了类似的更改。例如,Poll 现在在 Unix 上只是一个文件描述符。

使用 Events 结构处理事件的方式也发生了变化。在 0.6.10 版本中,索引访问已被弃用,并替换为迭代 API。索引访问在 0.7 版本中已完全移除,迭代 API 已更改为返回对 Event 的引用,而不是进行复制。Event 上的所有方法只需要引用,因此这不应该是一个问题。

移除用户空间队列和弃用类型

Mio 0.6 版本有一个用户空间队列,可通过 SetReadinessRegistration 类型访问,但在 0.7 版本中已将其移除,因为它被认为超出了 Mio 的范围。需要用户空间队列的用户可以使用 crossbeam crate 中找到的队列类型之一。

用户空间队列的一个用例是从另一个线程唤醒轮询线程(即调用 Poll::poll)。为了支持此用例,引入了一种新的 Waker 类型。调用 Waker::wake 将唤醒关联的 Poll

注册和 I/O 资源使用变更

到目前为止,0.7 版本面向用户最大的变化是 Mio 注册 I/O 源的方式。在 0.6 版本中,资源使用 TokenReadyPollOpt 参数进行注册。例如,以下代码将注册具有可读兴趣和边缘触发的 socket

poll.register(&socket, Token(0), Ready::readable(), PollOpt::edge())?;

如上所述,Event 类型已更改为原生操作系统类型的包装器。反过来,这移除了 Ready 类型,转而使用 Event 上的方法来检查就绪指示器并获取 Token。注册中使用的 Ready 类型已更改为 Interest,以更好地反映其用途。Interest 的 API 也进行了更改,以利用(某种程度上)新的关联常量,确保不再可能注册具有空兴趣的事件源。

在 0.7 版本中,Mio 使用边缘触发注册所有源,无需 PollOpt。边缘触发的使用将在下面解释。

在 0.6 版本中定义如何注册事件源的 trait 称为 Evented。这已更改为 Source,现在称为 event::Source,因为该类型位于 event 模块内。event::Source 具有与 Evented 相同的三种方法:registerreregisterderegister,但已按上述方式进行了更改。

最后,注册函数已从 Poll 移动到新的 Registry 类型,该类型可以 try_clone 并用于从不同线程注册源。总而言之,这意味着现在使用可读兴趣注册相同的 socket 看起来像这样

poll.registry().register(&socket, Token(0), Interest::READABLE)?;

迁移到边缘触发

如前所述,Mio 现在使用边缘触发注册所有 I/O 源,这意味着一次性触发和电平触发的用户需要更改他们响应事件的方式。

一个问题是,电平和一次性触发的某些用途显示了操作系统之间的差异,而 Mio 无法在不产生不可接受的开销的情况下取消这些差异。我们希望 Mio 提供良好的跨平台体验,因此我们决定使所有 I/O 源都采用边缘触发。这样,行为在所有平台上都是相同的。

以前使用一次性触发的用户现在应该在收到事件后 deregister I/O 源。

电平触发的用户基本上需要在所有 I/O 操作周围放置一个循环。当使用边缘触发时,用户需要在收到事件后执行 I/O。例如,在响应读取事件时,用户必须从 I/O 源读取,直到它阻塞(它返回 io::Error,其类型为 io::ErrorKind::WouldBlock)。只有当操作返回 WouldBlock 错误时,Mio 才会报告该 I/O 源上该 Interest 的更多事件。以下是如何使用边缘触发从 TcpStream 读取的示例。

let stream: TcpStream = ...;

// When we polled we received a readable event for our `TcpStream`.

let mut buf = [0; 4096];
// With edge triggers we need to read all data available on the socket.
loop {
    let bytes_read = match stream.read(&mut buf) {
        // Read successful, proceed like normal.
        Ok(bytes_read) => bytes_read,
        // No more data to read at the moment. We will receive another event
        // once more data is available to read.
        Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => break,
        // Got interrupted, try again.
        Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue,
        // Hit an actual error.
        Err(err) => return Err(err),
    };

    process_byte(&buf[0..bytes_read]);
}

注意:这需要发生在已注册事件源的所有 I/O 操作上,因此在使用自动化工具转换为新的注册 API 时要小心。

新增 Unix 套接字 API

net 模块中新增了 UnixListenerUnixStreamUnixDatagram 类型,它们支持 Unix 套接字,其 API 类似于标准库中找到的 API。

这些 API 目前仅在基于 Unix 的操作系统上受支持。

移除弃用的 API

在 Mio 0.6 版本中,各种类型(例如旧的事件循环相关类型和通道类型)已被弃用。在 0.7 版本中,所有弃用的类型都已移除。

移除对操作系统的支持

已移除对以下操作系统的支持

  • 低于 2.6.27 版本的 Linux(以及 glibc 2.9),我们正在使用较新 Linux 版本中不存在的较新 API,特别是 eventfd(2)socket(2) 中的 SOCK_NONBLOCKSOCK_CLOEXEC 选项以及 epoll_create1(2)
  • Fuchsia:没有 CI 覆盖率,我们也没有足够的维护人员来正确支持它。
  • Bitrig:该操作系统的开发似乎已停止,并且 rustc 不再支持它。

提高最低支持 Rust 版本

在撰写本文时,最低支持的 Rust 版本为 1.36。对于 1.0 版本,我们的目标是 Rust 1.39 版本,因为这是 async 变得稳定的版本(许多依赖 crate 正在使用的一项主要功能)。因此,请保持您的编译器更新!

维护者变更

除了代码之外,我们还进行了一些管理变更。存储库已移至 Tokio 组织,并且多位新人加入了团队。

—Thomas de Zeeuw