From f2ae9823cb68de2c97c48556c09ce3efef4d6afd Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 13 May 2026 23:28:16 +0800 Subject: [PATCH] fix(proxy): move client-request counters out of per-attempt loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three statistics-shape issues fixed together so the dashboard reflects client requests, not provider attempts: 1. active_connections never moved off zero — the field had no caller in the entire crate. Wrap forward_with_retry into a thin entry point that saturating_add(1) on enter and saturating_sub(1) on exit; every inner return path is covered automatically. 2. total_requests counted attempts, not requests. A single client call that failed over P1 -> P2 -> success was recorded as total=2 / success=1 -> 50% success rate. Move the increment and the last_request_at refresh into the wrapper so they fire once per client request regardless of how many providers were tried. 3. current_provider / current_provider_id stay inside the inner loop because they are intentionally per-attempt ("what am I trying right now?") — moving them would break the live-failover indicator. Refactor: split forward_with_retry into a public wrapper + private forward_with_retry_inner. Every existing `return Err(...)` inside inner remains correct because the wrapper always runs the decrement on its return. --- src-tauri/src/proxy/forwarder.rs | 41 ++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index bede9a842..418147210 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -156,13 +156,44 @@ impl RequestForwarder { /// 转发请求(带故障转移) /// + /// 这是 thin wrapper:在客户端请求维度记一次 `total_requests` / 调整 + /// `active_connections` / 刷新 `last_request_at`,无论 inner 走哪条出口路径, + /// 出口处都会把 `active_connections` 回收。Per-attempt 维度(成功/失败/熔断 + /// 等)仍由 inner 内自行更新 `success_requests` / `failed_requests`。 + pub async fn forward_with_retry( + &self, + app_type: &AppType, + endpoint: &str, + body: Value, + headers: axum::http::HeaderMap, + extensions: Extensions, + providers: Vec, + ) -> Result { + { + let mut s = self.status.write().await; + s.total_requests = s.total_requests.saturating_add(1); + s.active_connections = s.active_connections.saturating_add(1); + s.last_request_at = Some(chrono::Utc::now().to_rfc3339()); + } + let result = self + .forward_with_retry_inner(app_type, endpoint, body, headers, extensions, providers) + .await; + { + let mut s = self.status.write().await; + s.active_connections = s.active_connections.saturating_sub(1); + } + result + } + + /// 实际转发逻辑(不包含客户端维度的入口/出口计数) + /// /// # Arguments /// * `app_type` - 应用类型 /// * `endpoint` - API 端点 /// * `body` - 请求体 /// * `headers` - 请求头 /// * `providers` - 已选择的 Provider 列表(由 RequestContext 提供,避免重复调用 select_providers) - pub async fn forward_with_retry( + async fn forward_with_retry_inner( &self, app_type: &AppType, endpoint: &str, @@ -240,13 +271,15 @@ impl RequestForwarder { attempted_providers += 1; - // 更新状态中的当前Provider信息 + // 更新状态中的当前 Provider 信息(per-attempt 维度的标识) + // + // total_requests / last_request_at / active_connections 已由 + // forward_with_retry wrapper 在客户端请求维度统一处理,这里只刷 + // 新「正在尝试哪个 provider」的展示字段。 { let mut status = self.status.write().await; status.current_provider = Some(provider.name.clone()); status.current_provider_id = Some(provider.id.clone()); - status.total_requests += 1; - status.last_request_at = Some(chrono::Utc::now().to_rfc3339()); } // 转发请求(每个 Provider 只尝试一次,重试由客户端控制)