From 9cf0e605d4a7470dbbca1f375b7873a509bd70cb Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 1 Jul 2026 17:09:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E4=BF=AE=E5=A4=8D=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8/=E6=95=B0=E6=8D=AE=E5=BA=93=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=E5=A4=B1=E6=95=88?= =?UTF-8?q?=20Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use_future 旧实现在同步闭包体内捕获 status/loading/error signal, 导致这些 signal 每次 .set() 后都触发 use_future 重建新 loop, 旧 loop 同时继续运行,造成并发 loop 指数增长(请求爆炸), 实际效果等同于自动刷新完全失效或行为异常。 修复:采用 Dioxus 0.7 官方推荐模式—— - use_future 闭包直接返回 async move {},不在同步层读任何 signal - loop 内每次迭代读 refresh_interval/refresh_ms 的实时值 - 手动模式(interval = 0)改为 200ms yield + continue, 使切换到自动模式时最多延迟 200ms 生效,避免忙等 - sleep 结束后二次检查 interval,防止 sleep 期间用户切回手动时 仍然发起一次无意义请求 同时修复 DbStatusTab 和 ServerStatusTab 两处。 --- src/pages/admin/system.rs | 119 +++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/src/pages/admin/system.rs b/src/pages/admin/system.rs index 4cd5f1f..f92d302 100644 --- a/src/pages/admin/system.rs +++ b/src/pages/admin/system.rs @@ -173,42 +173,44 @@ fn DbStatusTab() -> Element { load_once(); }); - // 自动刷新:interval 变化时 use_future 重新执行,内部周期 sleep 后重新 load。 - // wasm32 才有意义(SSR 不轮询);sleep 用 web_sys 的 setTimeout 包成 JsFuture, - // 避免引入新依赖。interval 在 use_future 依赖里读取,变化即重建 future。 - use_future(move || { - // 在依赖闭包里读 interval,使 use_future 在它变化时重新执行。 - let interval_secs = refresh_interval(); - let mut status_f = status; - let mut loading_f = loading; - let mut error_f = error; - async move { - #[cfg(target_arch = "wasm32")] - { - let secs = interval_secs.unwrap_or(0); + // 自动刷新:使用官方推荐模式——一个永不重建的长生命周期 loop,在每次循环 + // 内部读取 refresh_interval 的当前值,自然响应间隔切换。 + // 旧做法在闭包同步体内读 status/loading/error signal,导致这些 signal 每次 + // .set() 后都触发 use_future 重建,产生多个并发 loop(请求爆炸)。 + use_future(move || async move { + #[cfg(target_arch = "wasm32")] + { + loop { + // 每次循环读最新 interval(signal 的 Copy 语义,直接调用即可)。 + let secs = refresh_interval().unwrap_or(0); if secs == 0 { - return; + // 手动模式:短暂 yield,让事件循环呼吸,避免忙等; + // 用户切换到自动模式后最多等 200ms 即响应。 + wasm_sleep(200).await; + continue; } - loop { - wasm_sleep(secs * 1000).await; - loading_f.set(true); - spawn(async move { - match get_db_status().await { - Ok(s) => { - status_f.set(Some(s)); - error_f.set(None); - } - Err(e) => error_f.set(Some(e.to_string())), + wasm_sleep(secs * 1000).await; + // 二次检查:sleep 期间用户可能切回手动。 + if refresh_interval().is_none() { + continue; + } + loading.set(true); + spawn(async move { + match get_db_status().await { + Ok(s) => { + status.set(Some(s)); + error.set(None); } - loading_f.set(false); - }); - } - } - #[cfg(not(target_arch = "wasm32"))] - { - let _ = (interval_secs, status_f, loading_f, error_f); + Err(e) => error.set(Some(e.to_string())), + } + loading.set(false); + }); } } + #[cfg(not(target_arch = "wasm32"))] + { + let _ = (status, loading, error, refresh_interval); + } }); // Option 非 Copy,读出来克隆一份供 rsx 消费。 @@ -461,39 +463,38 @@ fn ServerStatusTab() -> Element { load_once(); }); - // 自动刷新:refresh_ms 变化时重建 future。 - use_future(move || { - let interval_ms = refresh_ms(); - let mut status_f = status; - let mut loading_f = loading; - let mut error_f = error; - async move { - #[cfg(target_arch = "wasm32")] - { - let ms = interval_ms.unwrap_or(0); + // 自动刷新:同 DbStatusTab,采用官方推荐的单一长生命周期 loop 模式。 + // 闭包体内不读任何 signal(避免隐式依赖追踪导致重建),loop 内部实时读取。 + use_future(move || async move { + #[cfg(target_arch = "wasm32")] + { + loop { + let ms = refresh_ms().unwrap_or(0); if ms == 0 { - return; + wasm_sleep(200).await; + continue; } - loop { - wasm_sleep(ms).await; - loading_f.set(true); - spawn(async move { - match get_server_status().await { - Ok(s) => { - status_f.set(Some(s)); - error_f.set(None); - } - Err(e) => error_f.set(Some(e.to_string())), + wasm_sleep(ms).await; + if refresh_ms().is_none() { + continue; + } + loading.set(true); + spawn(async move { + match get_server_status().await { + Ok(s) => { + status.set(Some(s)); + error.set(None); } - loading_f.set(false); - }); - } - } - #[cfg(not(target_arch = "wasm32"))] - { - let _ = (interval_ms, status_f, loading_f, error_f); + Err(e) => error.set(Some(e.to_string())), + } + loading.set(false); + }); } } + #[cfg(not(target_arch = "wasm32"))] + { + let _ = (status, loading, error, refresh_ms); + } }); let current = status.read().clone();