#future #schedules #polling #order #execute #condition #async

schedwalk

在所有可能的轮询调度下测试 futures

1 个不稳定版本

0.1.0 2022年7月25日

#5#schedules

MIT 许可证

20KB
412

schedwalk

在所有可能的轮询调度下测试 futures。

并发系统很复杂。很容易错误地假设进度以某种特定的顺序发生(即竞态条件)。对于 Rust 中的异步系统,这可能是对 futures 轮询顺序的假设——对轮询调度的假设。

大多数异步测试运行时只会为您的测试执行一个调度,永远不会覆盖所有可能的调度。 schedwalk 是一个异步测试框架,允许您可靠地测试所有可能的调度。

示例

假设我们正在开发一个 Web 应用程序,并希望计算平均响应时间。我们可能会用以下两个任务来模拟

# use std::convert::identity as spawn;
# futures::executor::block_on(async {
use futures::{channel::mpsc, join};

let (sender, mut receiver) = mpsc::unbounded::<u32>();

let send_task = spawn(async move {
    sender.unbounded_send(23).unwrap();
    sender.unbounded_send(20).unwrap();
    sender.unbounded_send(54).unwrap();
});

let avg_task = spawn(async move {
    let mut sum = 0;
    let mut count = 0;
    while let Some(num) = receiver.try_next().unwrap() {
        sum += num;
        count += 1;
    }

    println!("average is {}", sum / count)
});

join!(send_task, avg_task);
# })

但是这个有竞态条件错误。如果 avg_tasksend_task 之前执行怎么办?那么 count 将为 0,然后我们将除以 0!我们隐含地假设一个任务在另一个任务之前执行。

那么我们如何创建一个触发上述竞态条件的测试呢?我们可以尝试在一个异步运行时(如 Tokio)下执行,但问题是它并不能保证失败的调度会被执行。事实上,在编写本文时,似乎单线程执行器 永远不会 触发失败。使用多线程执行器 可能会 触发失败,但没有保证。最好的情况是,我们创建了一个不可靠的测试。

理想情况下,我们希望在出现此类错误时,代码每次都确定性地失败。

现在介绍 schedwalk:一个用于在所有可能的调度下测试 futures 的库。使用 schedwalk 我们可以创建这样的测试

use schedwalk::{for_all_schedules, spawn};
use futures::{channel::mpsc, join};

for_all_schedules(|| async {
    let (sender, mut receiver) = mpsc::unbounded::<u32>();

    let send_task = spawn(async move {
        sender.unbounded_send(23).unwrap();
        sender.unbounded_send(20).unwrap();
        sender.unbounded_send(54).unwrap();
    });

    let avg_task = spawn(async move {
        let mut sum = 0;
        let mut count = 0;
        while let Some(num) = receiver.try_next().unwrap() {
            sum += num;
            count += 1;
        }

        println!("average is {}", sum / count)
    });

    join!(send_task, avg_task);
})

schedwalk 将在所有可能的调度下执行 future。在这种情况下,有两个:一个是 send_task 先执行,另一个是 avg_task 先执行。这将可靠地在我们的测试中触发错误。

为了便于调试,panic 和死锁将把轮询调度作为字符串打印到标准错误。将环境变量 SCHEDULE 设置为这将只执行确切的失败的调度。上面的例子将打印 panic in SCHEDULE=01。再次使用 SCHEDULE=01 cargo test example 执行测试时,将只执行那个确切的调度。

注意事项

关于 schedwalk 有一些建议需要注意。

  • schedwalk 假设确定性。每次必须以相同的顺序创建和轮询未来。也就是说,不能有影响轮询未来的顺序的线程局部或全局状态,也不能有外部IO影响系统。
  • schedwalk 将遍历所有可能的调度。在高并发轮询大量未来的情况下,这可能会迅速变得难以处理。

依赖项

~97KB