docs(core): explain managed coroutine root transfers and cleanup

This commit is contained in:
2026-11-15 17:50:44 +08:00
parent 8e190b25ec
commit 11703b7331
+70 -3
View File
@@ -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<ObjectRef, VmError> {
// 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。
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<CoroutineProgress, VmError> {
// 临时拥有根覆盖整个恢复,包括原生回调可能修改持有协程的对象图。
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<CoroutineProgress, VmError> {
// 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。
self.check_coroutine_entry()?;
// 软量子必须正数,否则没有可验证的推进工作。
if slice == 0 {
return Err(VmError::InvalidArgument("execution slice must be positive"));
}
// 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。
let coroutine = self.payload::<Coroutine>(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<bool, VmError> {
// 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。
self.check_coroutine_entry()?;
// 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。
let coroutine = self.payload::<Coroutine>(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
}