修复 Web 状态机在启动中停止时卡死
- 启动任务(含注册重试循环)此前是裸 tokio::spawn,stop 打不断: 服务器不可达时点击停止,状态永远卡在 Starting 或显示 Running 的死网络。 现在保存 JoinHandle,stop/restart 时先 abort_start_task 中断它, defer 守卫负责把状态迁移到 Stopped - 清理任务此前 spawn 在自己所等待的 task_group 里,形成自引用等待, 网络自行停止时永不返回;改为在任务组外等待 - 顺带修复 TaskGroup::wait_all_stopped 的丢失唤醒竞态: notified() 先 enable 注册再检查条件 - 补充 Starting 状态停止迁移、任务自然结束唤醒两个测试
This commit is contained in:
@@ -155,10 +155,14 @@ impl TaskGroup {
|
||||
|
||||
pub async fn wait_all_stopped(&self) {
|
||||
loop {
|
||||
// 先注册等待再检查条件,避免在检查与等待之间丢失唤醒
|
||||
let notified = self.inner.all_stopped_notify.notified();
|
||||
tokio::pin!(notified);
|
||||
notified.as_mut().enable();
|
||||
if self.inner.all_tasks_stopped() {
|
||||
return;
|
||||
}
|
||||
self.inner.all_stopped_notify.notified().await;
|
||||
notified.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,3 +257,33 @@ impl Drop for TaskGroupGuard {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// 所有任务自然结束后 wait_all_stopped 必须返回。
|
||||
/// 覆盖两个关键点:任务自然耗尽时 remove_task 置 stopped 并唤醒;
|
||||
/// 等待方先注册再检查,不会因竞态错过唤醒而永久挂起。
|
||||
#[tokio::test]
|
||||
async fn test_wait_all_stopped_after_natural_completion() {
|
||||
let manager = TaskGroupManager::new();
|
||||
let (group, _guard) = manager.create_task().unwrap();
|
||||
|
||||
let waiter = {
|
||||
let group = group.clone();
|
||||
tokio::spawn(async move { group.wait_all_stopped().await })
|
||||
};
|
||||
// 让 waiter 先进入等待
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
let _sub = group.spawn(async {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
});
|
||||
|
||||
tokio::time::timeout(std::time::Duration::from_secs(2), waiter)
|
||||
.await
|
||||
.expect("wait_all_stopped should return after all tasks complete")
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user