5 个版本

0.1.4 2023 年 8 月 23 日
0.1.3 2022 年 7 月 2 日
0.1.2 2022 年 3 月 14 日
0.1.1 2022 年 2 月 25 日
0.1.0 2022 年 2 月 25 日

#24 in 并发

Download history 29838/week @ 2024-04-12 23255/week @ 2024-04-19 19065/week @ 2024-04-26 22886/week @ 2024-05-03 28335/week @ 2024-05-10 32430/week @ 2024-05-17 32138/week @ 2024-05-24 28722/week @ 2024-05-31 21808/week @ 2024-06-07 30283/week @ 2024-06-14 22297/week @ 2024-06-21 23001/week @ 2024-06-28 34363/week @ 2024-07-05 40778/week @ 2024-07-12 33165/week @ 2024-07-19 19171/week @ 2024-07-26

133,446 每月下载量
271 个 crates (4 直接) 中使用

MIT/Apache

315KB
4.5K SLoC

corosensei

Crates.io

文档

变更日志

概述

本 crate 提供了一种在多个堆栈之间进行上下文切换的安全高效抽象,形式为 协程。协程是一个可以被暂停和恢复的函数,可以向调用者返回值。协程可以在其调用堆栈的任何位置挂起自己。除了从协程接收返回值外,每次恢复协程时还可以向其传递数据。

示例

use corosensei::{Coroutine, CoroutineResult};

fn main() {
    println!("[main] creating coroutine");

    let mut coroutine = Coroutine::new(|yielder, input| {
        println!("[coroutine] coroutine started with input {}", input);
        for i in 0..5 {
            println!("[coroutine] yielding {}", i);
            let input = yielder.suspend(i);
            println!("[coroutine] got {} from parent", input)
        }
        println!("[coroutine] exiting coroutine");
    });

    let mut counter = 100;
    loop {
        println!("[main] resuming coroutine with argument {}", counter);
        match coroutine.resume(counter) {
            CoroutineResult::Yield(i) => println!("[main] got {:?} from coroutine", i),
            CoroutineResult::Return(()) => break,
        }

        counter += 1;
    }

    println!("[main] exiting");
}

输出

[main] creating coroutine
[main] resuming coroutine with argument 100
[coroutine] coroutine started with input 100
[coroutine] yielding 0
[main] got 0 from coroutine
[main] resuming coroutine with argument 101
[coroutine] got 101 from parent
[coroutine] yielding 1
[main] got 1 from coroutine
[main] resuming coroutine with argument 102
[coroutine] got 102 from parent
[coroutine] yielding 2
[main] got 2 from coroutine
[main] resuming coroutine with argument 103
[coroutine] got 103 from parent
[coroutine] yielding 3
[main] got 3 from coroutine
[main] resuming coroutine with argument 104
[coroutine] got 104 from parent
[coroutine] yielding 4
[main] got 4 from coroutine
[main] resuming coroutine with argument 105
[coroutine] got 105 from parent
[coroutine] exiting coroutine
[main] exiting

支持的目标

本 crate 目前支持以下目标

ELF (Linux, BSD, 嵌入式系统等) Darwin (macOS, iOS 等) Windows
✅ x86_64 ✅ x86 ✅ x86 ✅ x86
✅ x86 ⚠️* AArch64
ARM ✅ x86 ✅ x86 ⚠️*
RISC-V ✅ x86 ⚠️* ⚠️*
LoongArch64 ✅ x86 ⚠️* ⚠️*
✅ x86 ⚠️* ⚠️*

* 在 x86 Windows 上不支持链式回溯。

如果您的目标尚未支持,请随时提交问题。

功能

恐慌传播

如果协程中发生恐慌,则恐慌将通过协程堆栈展开,然后继续从最后恢复它的调用者展开。一旦发生这种情况,协程被认为是完成的,并且无法再次恢复。

use std::panic::{catch_unwind, AssertUnwindSafe};

use corosensei::Coroutine;

fn main() {
    println!("[main] creating coroutine");

    let mut coroutine = Coroutine::new(|yielder, ()| {
        println!("[coroutine] yielding 42");
        yielder.suspend(42);

        println!("[coroutine] panicking");
        panic!("foobar");
    });

    println!("[main] resuming coroutine");
    let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
    println!(
        "[main] got value {} from coroutine",
        result.unwrap().as_yield().unwrap()
    );

    println!("[main] resuming coroutine");
    let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
    println!(
        "[main] caught panic \"{}\" from coroutine",
        result.unwrap_err().downcast_ref::<&'static str>().unwrap()
    );

    println!("[main] exiting");
}
[main] creating coroutine
[main] resuming coroutine
[coroutine] yielding 42
[main] got value 42 from coroutine
[main] resuming coroutine
[coroutine] panicking
thread 'main' panicked at 'foobar', examples/panic.rs:13:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[main] caught panic "foobar" from coroutine
[main] exiting

链式回溯

从协程内部获取的回溯将从一个协程上次恢复的点继续到父堆栈。这对于调试协程中的代码问题非常有帮助。

注意这个示例中的回溯显示协程最后是从 sub_function 恢复的

use backtrace::Backtrace;
use corosensei::Coroutine;

#[inline(never)]
fn sub_function(coroutine: &mut Coroutine<(), (), ()>) {
    coroutine.resume(());
}

fn main() {
    let mut coroutine = Coroutine::new(move |_, ()| {
        let trace = Backtrace::new();
        println!("{:?}", trace);
    });

    sub_function(&mut coroutine);
}
   0: backtrace::main::{{closure}}
             at examples/backtrace.rs:11:21
      corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func::{{closure}}
             at src/coroutine.rs:174:62
      corosensei::unwind::catch_unwind_at_root
             at src/unwind.rs:43:16
   1: corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func
             at src/coroutine.rs:174:30
   2: stack_init_trampoline
   3: corosensei::arch::x86_64::switch_and_link
             at src/arch/x86_64.rs:302:5
      corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume_inner
             at src/coroutine.rs:256:13
      corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume
             at src/coroutine.rs:235:19
      backtrace::sub_function
             at examples/backtrace.rs:6:5
   4: backtrace::main
             at examples/backtrace.rs:15:5

链式回溯使用平台的标准展开元数据和调试器、性能分析器以及许多其他工具兼容。

析构时的清理

如果在协程挂起时将其丢弃,则将使用与panic相同的机制安全地展开协程的堆栈,这将丢弃堆栈上的任何局部变量。这是为了保持安全的API:只要堆栈上还有活动对象,就不能释放或重用协程的堆栈。

如果不需要此清理,则可以使用不安全的force_reset函数强制将协程标记为已完成。

陷阱处理

对于高级用例,此crate提供从协程中发生的陷阱(例如,段错误、堆栈溢出)中恢复的能力。此API是不安全的,应在信号/异常处理程序中使用:它将帮助设置信号/异常处理程序的返回,以便协程立即以给定的返回值退出,并将控制权返回给其父进程。

这对于运行具有固定堆栈限制的JIT编译代码非常有用:堆栈溢出可以由信号/异常处理程序捕获并轻松恢复。

Cargo功能

当所有Cargo功能都禁用时,此crate与#![no_std]兼容。

此crate上可用的Cargo功能标志如下

default-stack(默认启用)

此功能提供了一个DefaultStack实现,该实现可用于Coroutine类型。此堆栈使用操作系统API以保护页分配,并需要std

如果禁用此功能,则必须实现自己的堆栈类型,该类型实现Stack特质。

unwind(默认启用)

此功能添加了对以下功能的支持:

  • 在协程中展开panic以返回其调用者。
  • 通过force_unwind或当协程被丢弃时强制展开挂起的协程。

请注意,如果禁用此功能,并且当协程挂起时被丢弃(即它至少已恢复一次但尚未返回)时,程序将中止。

需要std

asm-unwind

此功能使用asm_unwind夜间功能,允许panic直接通过此crate中使用的内联汇编展开,这可以提高性能,因为它不需要作为Result跨堆栈边界传递。

意味着unwind

性能

Corosensei已经针对性能进行了大量优化,切换堆栈只需要几个CPU周期。

可以通过cargo bench提供两个基准测试。

  • "Coroutine switch"衡量恢复协程并使其返回其调用者所需的时间。
  • "Coroutine call"衡量创建一个立即返回的新协程所需的时间,调用它然后丢弃协程。

在Linux上运行时的基准测试结果

架构 CPU 频率 基准测试 时间 周期
ARM 苹果M1 Max 2.06GHz - 3.22GHz Coroutine switch 3.8665 ns N/A
ARM 苹果M1 Max 2.06GHz - 3.22GHz Coroutine call 6.4813 ns N/A
x86-64 AMD Ryzen 9 3950X 3.5GHz - 4.7GHz Coroutine switch 4.2867 ns 14.8249
x86-64 AMD Ryzen 9 3950X 3.5GHz - 4.7GHz Coroutine call 5.5082 ns 19.0069
ARM ARM Cortex-A72 1.6GHz Coroutine switch 16.278 ns N/A
ARM ARM Cortex-A72 1.6GHz Coroutine call 18.769 ns N/A
龙芯3A5000 2.3GHz Coroutine switch 9.2831 ns N/A
龙芯3A5000 2.3GHz Coroutine call 16.101 纳秒 N/A

版权信息

本库受到以下项目的启发:

许可证

根据以下任一许可证授权:

由您选择。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义,您有意提交的任何贡献,将按照上述方式双许可,无需任何额外条款或条件。

依赖项

~0-11MB
~72K SLoC