docs: specify managed coroutine ownership and interleaving regressions

This commit is contained in:
2026-10-23 20:01:58 +08:00
parent e6b27ba7b6
commit b33d322690
5 changed files with 133 additions and 1 deletions
+2
View File
@@ -19,3 +19,5 @@
| 新 VM 公开 API | vm.rs/eval.rs 与 lib.rs 导出 | 所属主题、示例、[tests 导航](../tests/README.md) |
core 不访问对象私有字段或 Heap;执行器只通过受校验的对象存储操作维护帧根。返回 [core](../README.md)。
独立托管调用帧:frame.rs 定义 CoroutineProgress 和私有可追踪帧载荷;eval.rs 提供 new_coroutine/resume_coroutine/cancel_coroutine,暂停帧以托管边保活,恢复时转回执行根。与跨量子只读续算分开,所有权及边界见 docs/runtime/bytecode.md。
+2
View File
@@ -15,3 +15,5 @@
核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。
execution.rs 同时覆盖宿主可选 assign_from 的全局/局部赋值、别名身份、拒绝替换与执行根清理;不在本地运行。
execution.rs 的 managed_coroutine 用例覆盖交替恢复不重放写入、累计预算错误释放、取消幂等和暂停帧引用环回收;本地只经宿主Clippy编译,不运行测试。
+119
View File
@@ -1503,3 +1503,122 @@ fn suspended_call_cancellation_and_total_budget_restore_read_only_state() {
vm.release(shared).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn managed_coroutines_interleave_without_replaying_writes() {
use endor_core::CoroutineProgress;
let mut vm = Vm::new().unwrap();
let globals = vm.new_dict().unwrap();
let code = make_code(
&mut vm,
&[
op(Opcode::LoadConst, 0),
op(Opcode::StoreGlobal, 0),
op(Opcode::LoadConst, 1),
op(Opcode::StoreGlobal, 1),
op(Opcode::LoadConst, 2),
simple(Opcode::ReturnValue),
],
vec![text("first"), text("second"), Value::Nil],
CodeLayout {
names: vec!["x".into(), "y".into()],
..CodeLayout::default()
},
);
let function = vm
.new_function("work", code, globals, vec![], vec![])
.unwrap();
let first = vm
.new_coroutine(function, &[], ExecutionLimits::default())
.unwrap();
let second = vm
.new_coroutine(function, &[], ExecutionLimits::default())
.unwrap();
assert!(matches!(
vm.resume_coroutine(first, 2).unwrap(),
CoroutineProgress::Pending { instructions: 2 }
));
assert!(matches!(
vm.resume_coroutine(second, 2).unwrap(),
CoroutineProgress::Pending { instructions: 2 }
));
vm.dict_set(globals, text("x"), text("interleaved"))
.unwrap();
assert!(matches!(
vm.resume_coroutine(first, 16).unwrap(),
CoroutineProgress::Complete {
value: Value::Nil,
instructions: 6
}
));
assert_eq!(
vm.dict_get(globals, &text("x")).unwrap(),
Some(text("interleaved"))
);
assert_eq!(
vm.dict_get(globals, &text("y")).unwrap(),
Some(text("second"))
);
assert!(vm.cancel_coroutine(second).unwrap());
assert!(!vm.cancel_coroutine(second).unwrap());
assert!(vm.resume_coroutine(first, 1).is_err());
for object in [first, second, function, code, globals] {
vm.release(object).unwrap();
}
assert_eq!(vm.object_count(), 0);
}
#[test]
fn parked_coroutine_cycles_are_collectable_without_cancellation() {
let mut vm = Vm::new().unwrap();
let globals = vm.new_dict().unwrap();
let code = return_constant(&mut vm, Value::Nil);
let function = vm
.new_function("cycle", code, globals, vec![], vec![])
.unwrap();
let coroutine = vm
.new_coroutine(function, &[], ExecutionLimits::default())
.unwrap();
vm.dict_set(globals, text("task"), Value::Object(coroutine))
.unwrap();
for object in [coroutine, function, code, globals] {
vm.release(object).unwrap();
}
assert!(vm.collect_cycles() > 0);
assert_eq!(vm.object_count(), 0);
}
#[test]
fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() {
use endor_core::CoroutineProgress;
let mut vm = Vm::new().unwrap();
let globals = vm.new_dict().unwrap();
let code = return_constant(&mut vm, Value::Nil);
let function = vm
.new_function("limited", code, globals, vec![], vec![])
.unwrap();
let coroutine = vm
.new_coroutine(
function,
&[],
ExecutionLimits {
max_frames: 4,
max_instructions: 1,
},
)
.unwrap();
assert!(matches!(
vm.resume_coroutine(coroutine, 1).unwrap(),
CoroutineProgress::Pending { instructions: 1 }
));
assert!(matches!(
vm.resume_coroutine(coroutine, 1),
Err(VmError::ExecutionLimit("instructions"))
));
assert!(!vm.last_traceback().is_empty());
assert!(!vm.cancel_coroutine(coroutine).unwrap());
for object in [coroutine, function, code, globals] {
vm.release(object).unwrap();
}
assert_eq!(vm.object_count(), 0);
}
+1 -1
View File
@@ -12,7 +12,7 @@
| 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
| ASDL、AST 与编辑器接口 | [AST](runtime/ast.md) | frontend/Endor.asdl |
| 名称分析、编译和装载 | [Compiler](runtime/compiler.md) | compiler/src |
| 字节码、函数、共享引用赋值、可恢复只读分片与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src |
| 字节码、函数、共享引用赋值、可恢复只读分片、独立托管调用帧与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src |
| 引用与 VM 生命周期 | [生命周期](runtime/lifecycle.md) | objects/src、core/src |
| UTF-8 与地区 | [文本编码](runtime/text-encoding.md) | frontend、objects |
| 嵌入及宿主入口 | [使用引擎](operations/README.md) | 独立宿主仓库 |
+9
View File
@@ -186,3 +186,12 @@ fn main() -> Result<(), endor_core::VmError> {
BuildClass(编号 29,无参数)消费四个栈值 `[name, baseType, fieldsDict, methodsDict]`,产生一个受管类对象;验证器按 4 入 1 出检查栈。字段字典按声明顺序保存字段名与注解名,方法字典保存 MakeFunction 创建的函数。VM 先验证基类、字段与方法关系,再创建完整类型;输入根在成功前保持在执行栈上,失败由帧统一清理。
LoadAttr 对实例自有属性返回原值;缺失时沿类型父链找方法并创建绑定方法。绑定方法持有 receiver 和选定的函数,调用时经已有非递归委托路径补入 self。类对象上的方法读取提供未绑定函数;type/name/base 元数据优先。宿主读取继承方法应使用返回拥有式结果的 bind_attr;get_attr 保持借用式读取,类型方法结果为未绑定函数。所有委托消耗同一指令预算。
## 独立托管调用帧
`Vm::new_coroutine(function, arguments, limits)` 返回拥有一份外部根的通用 coroutine 对象;`resume_coroutine(handle, slice)` 返回 `CoroutineProgress::Pending` 或带拥有式结果的 `Complete`。源码指令首次在 resume 执行,原生函数/委托可能在 new 的准备期间执行,仍受硬指令限额约束。该接口目前只提供分片执行基础,不等于 Endor async/await 语法或等待协议。
与只读续算不同,每个 coroutine 把自己的帧保存在同一 VM 的托管对象载荷中,多个调用可独立暂停;Pending 之间可执行普通调用和堆写入,没有跨量子的只读作用域。resume 不重放先前指令,局部槽、栈、返回类型及原生委托契约保持,所有量子共用原始硬指令/帧预算。原生调用仍是原子的,slice 不保证墙钟延迟。读写共享堆,无快照、事务或自动回滚。不能在只读执行/只读暂停期间创建、恢复或取消 coroutine。
暂停时,帧内原本的执行根转为载荷 trace 的托管边(包括重复边);恢复前先保活为执行根,再清空暂停载荷。函数边继续拥有 code/globals/builtins。运行中的 handle 临时保活,不能恢复或取消 Running/已结束帧;完成和错误均消耗帧,错误保留 traceback。`cancel_coroutine(handle)` 对尚未结束帧返回 true 并释放托管边,已结束返回 false;已发生的写入不回滚。调用方最终 release handle 和完成结果。丢弃未完成且不可达的 coroutine 不要求先 cancel,局部/全局指回 coroutine 的环可由正常循环回收处理,不把暂停帧永久登记为 VM 根。调度、任务归属、等待条件及取消策略由宿主实现,网络/GUI不进入内核。