fix(admin): 修复服务器/数据库状态自动刷新失效 Bug

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 两处。
This commit is contained in:
xfy 2026-07-01 17:09:39 +08:00
parent 404f85b732
commit 9cf0e605d4

View File

@ -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 {
// 每次循环读最新 intervalsignal 的 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<DbStatus> 非 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();