From 11703b73318cba0cfaf14c28801ffb84e80ce2de Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:50:44 +0800 Subject: [PATCH] docs(core): explain managed coroutine root transfers and cleanup --- crates/core/src/eval.rs | 73 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 617926a..d562cb9 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -328,18 +328,26 @@ impl Vm { /// Prepare a managed independently resumable call. Native delegation may /// run during preparation; source instructions start only on resume. /// The returned handle owns one external reference, like every allocation. + /// 宿主任务系统准备独立可恢复调用,返回一份协程拥有根;原生准备可发生在首次 resume 之前。 pub fn new_coroutine( &mut self, function: ObjectRef, arguments: &[Value], limits: ExecutionLimits, ) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 本次真正执行前清理旧诊断,错误才记录新的帧位置。 self.last_traceback.clear(); + // 首次调用准备也计入累计预算,后续恢复不能清零。 let mut executed = 0; + // prepare_call 可能直接完成原生调用或返回已持根的源码帧。 let prepared = self.prepare_call(function, arguments.to_vec(), &mut executed, limits)?; + // 源码帧暂存 Frames,立即结果暂存 Ready;两者尚持有执行根待转换。 let state = match prepared { + // 新源码帧先核对硬帧上限,零预算必须清理已取得的帧根。 PreparedCall::Frame(frame) => { + // 构造执行帧不等于已获执行许可,失败归还其全部拥有根。 if limits.max_frames == 0 { self.clear_frame(frame); return Err(VmError::ExecutionLimit("frames")); @@ -348,22 +356,29 @@ impl Vm { } PreparedCall::Value(value) => CoroutineState::Ready(value), }; - // Allocate traced edges before releasing the original execution roots. + // 先建立托管载荷强边,再归还准备阶段根,确保转换期间对象始终存活。 + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = Coroutine { state, limits, executed, }; + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 先快照原执行根,分配后载荷内部边接管这些引用。 coroutine.trace(&mut |reference| roots.push(reference)); + // 分配成功先建立内部边,失败则没有托管接管者;两种情况都要归还原执行根。 let result = self.allocate(coroutine); + // 逐项归还原执行根;成功路径已由载荷持边,错误路径则释放准备状态资源。 for root in roots { self.release(root)?; } result } + /// 创建/恢复/取消共用门禁,禁止在全局只读续执行或其他只读域内进行协程操作。 fn check_coroutine_entry(&self) -> Result<(), VmError> { + // 可变协程不持有全局只读域,也不能干扰 VM 已停放的只读调用。 if self.has_suspended_call() || self.objects.is_read_only() { return Err(VmError::UnsupportedOperation( "coroutine operation inside read-only execution", @@ -375,37 +390,52 @@ impl Vm { /// Resume one managed call, preserving cumulative hard limits. Other calls /// and heap writes may execute between Pending results. No prior instruction /// is replayed. Completion returns an owned value and consumes the frames. + /// 宿主推进一个协程量子,先临时保留协程自身,防回调移除其最后外部来源。 pub fn resume_coroutine( &mut self, reference: ObjectRef, slice: usize, ) -> Result { + // 临时拥有根覆盖整个恢复,包括原生回调可能修改持有协程的对象图。 self.retain(reference)?; + // 保存执行结果,使错误返回也经过下方协程临时根释放。 let result = self.resume_coroutine_inner(reference, slice); + // 归还仅由本次推进取得的保护根,不消耗调用者原有根。 self.release(reference)?; result } + /// 恢复状态、把载荷边转为执行根,执行后再停放或提交 Finished;外层负责保护协程本身。 fn resume_coroutine_inner( &mut self, reference: ObjectRef, slice: usize, ) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 软量子必须正数,否则没有可验证的推进工作。 if slice == 0 { return Err(VmError::InvalidArgument("execution slice must be positive")); } + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = self.payload::(reference, "coroutine")?; + // 硬限制来自创建时状态,不能在每个量子重新给予预算。 let limits = coroutine.limits; + // 从上次停放的累计计数继续,而不是从零执行。 let mut executed = coroutine.executed; + // 只复制状态表示/Arc,不因 clone 增加托管计数;先由旧载荷边保证存活。 let state = match &coroutine.state { CoroutineState::Frames(frames) => CoroutineState::Frames(frames.clone()), CoroutineState::Ready(value) => CoroutineState::Ready(value.clone()), _ => return Err(VmError::InvalidArgument("coroutine is running or finished")), }; + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 列出恢复为活跃执行时所需全部根;Ready 标量没有对象根。 match &state { + // 帧身份沿同一 trace 清单转换,不复制执行行为或重新绑定实参。 CoroutineState::Frames(frames) => { + // 复用 frame::trace_roots,确保与活跃帧清理清单完全对应。 for frame in frames.iter() { frame.trace_roots(&mut |root| roots.push(root)); } @@ -413,11 +443,14 @@ impl Vm { CoroutineState::Ready(Value::Object(root)) => roots.push(*root), _ => {} } - // Transfer edges to execution roots transactionally, before replacing - // the managed payload. Duplicate edges must retain duplicate roots. + // 恢复时方向相反:先取得每份执行根,再替换旧载荷以释放内部边;重复边对应重复根。 + // 事务式取得新执行根;中途失败仅归还本轮已成功 retain 的部分。 let mut retained = Vec::new(); + // 逐项取得恢复所需执行根,旧载荷在全部取得成功前保持不变。 for root in roots { + // 任何一个根无法保留就不替换旧载荷,原停放状态仍有效。 if let Err(error) = self.retain(root) { + // 归还本轮新加根,不释放仍属于旧载荷的内部边。 for root in retained { self.release(root)?; } @@ -425,6 +458,7 @@ impl Vm { } retained.push(root); } + // 状态替换也可能失败,调用方必须清理已保留根或尚未交付的结果。 if let Err(error) = self.replace_payload( reference, Coroutine { @@ -433,17 +467,23 @@ impl Vm { executed, }, ) { + // 归还本轮新加根,不释放仍属于旧载荷的内部边。 for root in retained { self.release(root)?; } return Err(error); } + // 本次真正执行前清理旧诊断,错误才记录新的帧位置。 self.last_traceback.clear(); + // Running 替换释放旧载荷 Arc 后,快照 Arc 应成为唯一所有者,可取回帧向量。 let mut frames = match state { + // 帧身份沿同一 trace 清单转换,不复制执行行为或重新绑定实参。 CoroutineState::Frames(frames) => { std::sync::Arc::try_unwrap(frames).expect("private frame storage has one owner") } + // 准备阶段已完成,直接提交 Finished,再把新取得的结果根交给调用者。 CoroutineState::Ready(value) => { + // 状态替换也可能失败,调用方必须清理已保留根或尚未交付的结果。 if let Err(error) = self.replace_payload( reference, Coroutine { @@ -462,20 +502,29 @@ impl Vm { } _ => unreachable!(), }; + // 软边界按累计计数增加,saturating_add 防边界绕回,硬上限仍独立检查。 let slice_end = executed.saturating_add(slice); + // 允许 await 停放;None 既可能是软量子结束,也可能等待对象 Pending。 let result = self.eval_frames(&mut frames, limits, &mut executed, slice_end, true); + // 需要停放:先分析是否 await,再把完整帧/计数提交回托管载荷。 if let Ok(None) = result { + // waiting 只取最内层帧等待对象;栈顶操作数留存,恢复时不会重放创建它的调用。 let waiting = frames.last().filter(|frame| frame.waiting).map(|frame| { + // 已执行 Await 证明栈顶是对象,否则属于执行器内部不变量破坏。 match Self::top_value(frame).expect("await operand remains on stack") { Value::Object(reference) => reference, _ => unreachable!(), } }); + // 把帧向量重新装进共享快照,内部边接管前仍持原执行根。 let frames = std::sync::Arc::new(frames); + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 复用 frame::trace_roots,确保与活跃帧清理清单完全对应。 for frame in frames.iter() { frame.trace_roots(&mut |root| roots.push(root)); } + // 先提交 Frames 与完整强边,发布成功前不能释放原执行根造成悬空。 let published = self.replace_payload( reference, Coroutine { @@ -484,10 +533,13 @@ impl Vm { executed, }, ); + // 逐项归还原执行根;成功路径已由载荷持边,错误路径则释放准备状态资源。 for root in roots { self.release(root)?; } + // 归还原执行根后传播发布错误;失败不会交付虚假的 Pending 状态。 published?; + // Waiting 只返回借用等待身份,图由停放协程帧维持,调度者须保留协程根。 if let Some(awaitable) = waiting { return Ok(CoroutineProgress::Waiting { awaitable, @@ -498,6 +550,7 @@ impl Vm { instructions: executed, }); } + // 在帧清理前提取回溯,保存 current_offset 而非下一指令位置。 if result.is_err() { self.last_traceback = frames .iter() @@ -509,9 +562,11 @@ impl Vm { }) .collect(); } + // 终态清理所有剩余活跃帧根,成功返回值已先从栈中移出。 while let Some(frame) = frames.pop() { self.clear_frame(frame); } + // 无论成功或失败都清空运行状态,保留预算/计数供诊断,Finished 不再持帧图。 let finished = self.replace_payload( reference, Coroutine { @@ -520,8 +575,11 @@ impl Vm { executed, }, ); + // 成功必须同时完成状态提交;提交失败归还结果,执行错误路径仍须处理收尾结果。 match result { + // 结果拥有根仅在 Finished 提交成功后移交给宿主。 Ok(Some(value)) => { + // 结果已产生但状态提交失败,不能泄漏该结果根。 if let Err(error) = finished { self.drop_root(value); return Err(error); @@ -531,6 +589,7 @@ impl Vm { instructions: executed, }) } + // 此处先确保 Finished 发布成功,再返回原执行错误。 Err(error) => { finished?; Err(error) @@ -542,16 +601,23 @@ impl Vm { /// Cancellation releases managed frame edges; dropping an unreachable /// coroutine has the same ownership effect, including cycles through locals. /// Writes performed by previous quanta are never rolled back. + /// 宿主取消停放帧或准备结果,用 Finished 替换以释放载荷边;不回滚此前写入。 pub fn cancel_coroutine(&mut self, reference: ObjectRef) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = self.payload::(reference, "coroutine")?; + // 正执行状态拒绝取消,避免在 Rust 回调重入时移除执行者状态。 if matches!(coroutine.state, CoroutineState::Running) { return Err(VmError::InvalidArgument("coroutine is running")); } + // 重复取消是正常 false,不再修改载荷版本。 if matches!(coroutine.state, CoroutineState::Finished) { return Ok(false); } + // 硬限制来自创建时状态,不能在每个量子重新给予预算。 let limits = coroutine.limits; + // 取消保留累计诊断计数,不把已经执行的副作用或预算消耗抹除。 let executed = coroutine.executed; self.replace_payload( reference, @@ -566,6 +632,7 @@ impl Vm { /// Outer-to-inner execution locations from the last failed call/execute. /// These are copied metadata, not heap references or language exceptions. + /// 宿主错误报告借用最近失败的复制位置元数据,不保留故障对象图。 pub fn last_traceback(&self) -> &[ExecutionLocation] { &self.last_traceback }