关于 async-io-model
这份指南面向在 Rust 中实现可能等待 I/O 的操作,介绍 Turso 的协作式 I/O 模型。内容涵盖显式状态机、完成事件处理,以及操作让出后再次调用时如何避免错误。
主要功能
- 01基于 IOResult 的协作式让出
- 02Completion 与 CompletionGroup 处理
- 03I/O 状态机模式
- 04重入问题及其规避方法
- 05模拟与故障注入测试
适用场景
- 01实现可能等待 I/O 的 Rust 操作
- 02聚合并取消多个完成事件
- 03避免让出后重复修改状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ tursodatabase/ turso --skill async-io-model安装后在 Claude Code 中输入/async-io-model确认已加载。
详细介绍
异步 I/O 模型指南
Turso 使用协作式让出和显式状态机,而不使用 Rust async/await。
核心类型
pub enum IOCompletions {
Single(Completion),
}
#[must_use]
pub enum IOResult<T> {
Done(T), // Operation complete, here's the result
IO(IOCompletions), // Need I/O, call me again after completions finish
}
返回 IOResult 的函数必须反复调用,直到返回 Done。
Completion 和 CompletionGroup
Completion 跟踪单个 I/O 操作:
pub struct Completion { /* ... */ }
impl Completion {
pub fn finished(&self) -> bool;
pub fn succeeded(&self) -> bool;
pub fn get_error(&self) -> Option<CompletionError>;
}
要等待多个 I/O 操作,请使用 CompletionGroup:
let mut group = CompletionGroup::new(|_| {});
// Add individual completions
group.add(&completion1);
group.add(&completion2);
// Build into single completion that finishes when all complete
let combined = group.build();
io_yield_one!(combined);
CompletionGroup 的功能:
- 将多个 completion 聚合为一个
- 在所有操作完成时(或任一操作出错时)调用回调
- 可以嵌套组(将一个组的 completion 添加到另一个组)
- 可通过
group.cancel()取消
辅助宏
return_if_io!
解包 IOResult,并将 IO 变体向上传播到调用栈:
let result = return_if_io!(some_io_operation());
// Only reaches here if operation returned Done
io_yield_one!
让出一个 completion:
io_yield_one!(completion); // Returns Ok(IOResult::IO(Single(completion)))
状态机模式
可能让出执行的操作使用显式状态枚举:
enum MyOperationState {
Start,
WaitingForRead { page: PageRef },
Processing { data: Vec<u8> },
Done,
}
函数会循环,根据状态进行匹配并转换状态:
fn my_operation(&mut self) -> Result<IOResult<Output>> {
loop {
match &mut self.state {
MyOperationState::Start => {
let (page, completion) = start_read();
self.state = MyOperationState::WaitingForRead { page };
io_yield_one!(completion);
}
MyOperationState::WaitingForRead { page } => {
let data = page.get_contents();
self.state = MyOperationState::Processing { data: data.to_vec() };
// No yield, continue loop
}
MyOperationState::Processing { data } => {
let result = process(data);
self.state = MyOperationState::Done;
return Ok(IOResult::Done(result));
}
MyOperationState::Done => unreachable!(),
}
}
}
可重入性:关键陷阱
在让出点之前修改状态,会导致重新进入时出现错误。
错误示例
fn bad_example(&mut self) -> Result<IOResult<()>> {
self.counter += 1; // Mutates state
return_if_io!(something_that_might_yield()); // If yields, re-entry will increment again!
Ok(IOResult::Done(()))
}
如果 something_that_might_yield() 返回 IO,调用方会等待 completion,然后再次调用 bad_example()。counter 会被递增两次(或更多次)。
正确做法:在让出后修改状态
fn good_example(&mut self) -> Result<IOResult<()>> {
return_if_io!(something_that_might_yield());
self.counter += 1; // Only reached once, after IO completes
Ok(IOResult::Done(()))
}
正确做法:使用状态机
enum State { Start, AfterIO }
fn good_example(&mut self) -> Result<IOResult<()>> {
loop {
match self.state {
State::Start => {
// Don't mutate shared state here
self.state = State::AfterIO;
return_if_io!(something_that_might_yield());
}
State::AfterIO => {
self.counter += 1; // Safe: only entered once
return Ok(IOResult::Done(()));
}
}
}
}
常见的可重入性错误
| 模式 | 问题 |
|---|---|
vec.push(x); return_if_io!(...) | Vec 在每次重新进入时都会增长 |
idx += 1; return_if_io!(...) | 索引会多次递增 |
map.insert(k,v); return_if_io!(...) | 插入重复项或覆盖已有项 |
flag = true; return_if_io!(...) | 通常没问题,但要检查逻辑 |
状态枚举设计
在状态变体中编码进度:
// Good: index is part of state, preserved across yields
enum ProcessState {
Start,
ProcessingItem { idx: usize, items: Vec<Item> },
Done,
}
// Loop advances idx only when transitioning states
ProcessingItem { idx, items } => {
return_if_io!(process_item(&items[idx]));
if idx + 1 < items.len() {
self.state = ProcessingItem { idx: idx + 1, items };
} else {
self.state = Done;
}
}
Turso 实现
关键文件:
core/types.rs-IOResult、IOCompletions、return_if_io!、return_and_restore_if_io!core/io/completions.rs-Completion、CompletionGroupcore/util.rs-io_yield_one!宏core/state_machine.rs- 通用StateMachine包装器core/storage/btree.rs- 多个状态机示例core/storage/pager.rs-CompletionGroup用法示例
测试异步代码
可重入性错误通常只会在特定的 I/O 时序下显现。请使用:
- 确定性模拟(
testing/simulator/) - Whopper 并发 DST(
testing/concurrent-simulator/) - 故障注入,在不同位置强制让出执行
参考资料
docs/manual.md中关于 I/O 的章节
评论
支持 Markdown