fix(system): 备份/恢复任务轮询永不启动导致按钮卡死在 loading
点击「创建备份」后接口返回 task id,但按钮一直转圈、列表不刷新。 根因是 AGENTS.md 踩坑记录里的「Reactive hooks 不追踪普通 props/snapshot」: 进度轮询 use_future 在挂载时把 active_task_id 快照进 _task_id_for_poll (彼时为 None),use_future 只运行一次即 return;用户点击后 create_backup 返回 task id 并 active_task_id.set(Some(id)),但 future 已结束、永不重跑 → 轮询不启动 → busy 永远 true。恢复确认 use_future 有同样的快照陷阱 (_pending_for_confirm),导致点「恢复」连确认框都不弹。 改用与 DbStatusTab 自动刷新一致的长生命周期 loop 模式:use_future 只跑 一次,内部 loop 每轮迭代读 active_task_id() / pending_restore() 信号, 空闲时 200ms yield 呼吸。这样信号更新在下一轮迭代即被感知,无需依赖 ReactiveContext 对挂载期快照的订阅。
This commit is contained in:
parent
cf14aae17f
commit
2e40836480
@ -1254,23 +1254,34 @@ fn BackupTab() -> Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 恢复确认:pending_restore 被设置时弹出原生 confirm,确认后发起 restore_backup
|
// 恢复确认:pending_restore 被设置时弹出原生 confirm,确认后发起 restore_backup
|
||||||
// 并开始进度轮询。用 use_future 响应 pending_restore 变化。
|
// 并开始进度轮询。
|
||||||
// _pending_for_confirm 被 clone 进 async move(FnMut 可重入),用 Option::clone 兜底。
|
//
|
||||||
let _pending_for_confirm = pending_restore.read().clone();
|
// 采用与 DbStatusTab 自动刷新相同的长生命周期 loop 模式:在每次循环内部读
|
||||||
|
// pending_restore() 信号。原先用 use_future 在挂载时快照一次 pending_restore
|
||||||
|
//(彼时为 None),use_future 仅运行一次即 return,用户后续点击"恢复"设置
|
||||||
|
// pending_restore 后 future 不会重跑(Dioxus 的 ReactiveContext 只订阅闭包
|
||||||
|
// *执行期间* 读到的 signal,同步体的 .read().clone() 不建立订阅)→ 确认框
|
||||||
|
// 永不弹出。改成 loop 内读信号后,下一轮迭代即拿到最新值。
|
||||||
use_future(move || {
|
use_future(move || {
|
||||||
let mut pending_restore = pending_restore;
|
let mut pending_restore = pending_restore;
|
||||||
let mut busy = busy;
|
let mut busy = busy;
|
||||||
let mut active_progress = active_progress;
|
let mut active_progress = active_progress;
|
||||||
let mut active_task_id = active_task_id;
|
let mut active_task_id = active_task_id;
|
||||||
let mut error = error;
|
let mut error = error;
|
||||||
let pending_for_confirm = _pending_for_confirm.clone();
|
|
||||||
async move {
|
async move {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
let fname = match pending_for_confirm {
|
loop {
|
||||||
|
let fname = match pending_restore() {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
None => return,
|
None => {
|
||||||
|
// 空闲:短 yield 让事件循环呼吸,避免忙等。
|
||||||
|
wasm_sleep(200).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
// 取到了:先清空 pending,防止 loop 重入时再次确认。
|
||||||
|
pending_restore.set(None);
|
||||||
let confirmed = web_sys::window()
|
let confirmed = web_sys::window()
|
||||||
.and_then(|w| {
|
.and_then(|w| {
|
||||||
w.confirm_with_message(&format!(
|
w.confirm_with_message(&format!(
|
||||||
@ -1291,37 +1302,46 @@ fn BackupTab() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 无论确认与否都清空 pending
|
// 处理完一轮后回到 loop 顶部继续等待下一个 pending。
|
||||||
pending_restore.set(None);
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let _ = (pending_restore, busy, active_progress, active_task_id, error, pending_for_confirm);
|
let _ = (pending_restore, busy, active_progress, active_task_id, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 任务进度轮询:active_task_id 存在时每 1.5s 拉取进度,Done/Failed 后停止 + 刷新列表
|
// 任务进度轮询:active_task_id 存在时每 1.5s 拉取进度,Done/Failed 后停止 + 刷新列表。
|
||||||
// _task_id_for_poll 被 clone 进 async move(FnMut 可重入)。
|
//
|
||||||
let _task_id_for_poll = active_task_id.read().clone();
|
// 同样用长生命周期 loop + 循环内读 active_task_id() 的模式。原先在挂载时把
|
||||||
|
// active_task_id 快照进 _task_id_for_poll(彼时为 None),use_future 只跑一次
|
||||||
|
// 即 return;用户点"创建备份"后 create_backup 返回 task id 并设置信号,但
|
||||||
|
// future 已结束 → 轮询永不启动,busy 永远为 true(用户报告的 bug)。
|
||||||
use_future(move || {
|
use_future(move || {
|
||||||
let mut active_task_id = active_task_id;
|
let mut active_task_id = active_task_id;
|
||||||
let mut active_progress = active_progress;
|
let mut active_progress = active_progress;
|
||||||
let mut backups_f = backups;
|
let mut backups_f = backups;
|
||||||
let mut busy_f = busy;
|
let mut busy_f = busy;
|
||||||
let task_id_for_poll = _task_id_for_poll.clone();
|
|
||||||
async move {
|
async move {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
let tid = match task_id_for_poll {
|
loop {
|
||||||
|
let tid = match active_task_id() {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => return,
|
None => {
|
||||||
|
// 空闲:短 yield,最多 200ms 后响应新任务。
|
||||||
|
wasm_sleep(200).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
// 有任务在途:进入 1.5s 轮询,直到 Done/Failed/出错。
|
||||||
loop {
|
loop {
|
||||||
wasm_sleep(1500).await;
|
wasm_sleep(1500).await;
|
||||||
match get_task_progress(tid.clone()).await {
|
match get_task_progress(tid.clone()).await {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
let done = p.status == TaskStatus::Done || p.status == TaskStatus::Failed;
|
let done = p.status == TaskStatus::Done
|
||||||
|
|| p.status == TaskStatus::Failed;
|
||||||
active_progress.set(Some(p));
|
active_progress.set(Some(p));
|
||||||
if done {
|
if done {
|
||||||
// 刷新列表(备份完成后新文件出现)并清理任务态
|
// 刷新列表(备份完成后新文件出现)并清理任务态
|
||||||
@ -1340,10 +1360,12 @@ fn BackupTab() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 内层 loop 退出后回到外层,继续等待下一个任务或空闲。
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let _ = (active_task_id, active_progress, backups_f, busy_f, task_id_for_poll);
|
let _ = (active_task_id, active_progress, backups_f, busy_f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user