docs(core): explain managed coroutine root transfers and cleanup
This commit is contained in:
+70
-3
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user