测速的超时钳制:2 / 8 / 30 秒这三个数是怎么用的
在 cc-switch 的源码里,端点测速这件小事只占一个文件,开头三行就是全部的可调空间:
const DEFAULT_TIMEOUT_SECS: u64 = 8;
const MAX_TIMEOUT_SECS: u64 = 30;
const MIN_TIMEOUT_SECS: u64 = 2;
位置是 src-tauri/src/services/speedtest.rs 的第 8 到 10 行。很多人第一眼会把它们读成「三档超时可以选」——2 秒快测、8 秒常规、30 秒宽容。不是这么回事。这三个数扮演的是三个完全不同的角色:8 是没传值时的默认,2 和 30 是传了值也会被折回去的上下界。
以下全部基于我们本地 clone 的 cc-switch 仓库快照 c39c903(提交日期 2026-08-10),仓库内版本号 3.19.2。我们只读源码文本,没有安装也没有运行过这个桌面应用。
反直觉的那一处:这是 clamp,不是校验
真正决定这三个数怎么生效的,是 speedtest.rs:126-129 那几行:传进来的超时秒数如果是空的,取 DEFAULT_TIMEOUT_SECS;拿到值之后统一走一次 secs.clamp(MIN_TIMEOUT_SECS, MAX_TIMEOUT_SECS)。
clamp 的语义是把越界的值折到边界上,而不是把它挡回去。落到这三个常量上就是:
| 上层传入 | 实际生效 | 原因 |
|---|---|---|
| 不传 | 8 秒 | 取 DEFAULT_TIMEOUT_SECS |
| 0 或 1 | 2 秒 | 被 MIN_TIMEOUT_SECS 抬上来 |
| 2 到 30 之间 | 原值 | 落在区间内,不改 |
| 60、300、9999 | 30 秒 | 被 MAX_TIMEOUT_SECS 压下来 |
这张表的读法:左边一列是调用方给出的意图,右边一列是这次测速真正等待的上限。两列不相等的那几行,就是容易产生误解的地方——你以为自己配了个很宽松的超时,实际拿到的仍然是 30 秒;你以为自己配了个「不超时」的 0,实际拿到的是这三个数里最短的 2 秒。这个折叠过程发生在函数内部,调用方拿回来的是测速结果本身,不是「你的参数被改过」的回执。
要留意的是,这里说的是代码里的默认与边界,不是「你用起来会等多久」。请求实际花多长时间取决于网络与上游,本文不对运行结果做任何推算。
第二处反直觉:一次测速发的是两次请求
speedtest.rs:73-86 这一段还有个更容易被忽略的设计:对每一个待测 URL,代码先发一次热身请求并直接忽略结果,第二次请求才开始计时,第二次的结果才是返回给上层的那个延迟。注释给出的理由是复用连接、绕过首包惩罚。
这件事改变了「延迟」这个数字的含义。它衡量的不是冷启动——不是 DNS 解析、TCP 握手、TLS 协商全走一遍的代价,而是连接已经热过之后再打一发的往返。如果你想拿这个数去回答「我第一次连它要等多久」,那问的不是同一件事。
它同时也改变了超时这个参数的分量。钳制出来的秒数是每次请求各自的上限,而一次测速里有两次请求。所以这个数字既不是「整轮测速的总耗时上限」,也不是「你会看到的那个延迟的上限」。想把 2 / 8 / 30 用对,先把「一轮 = 热身 + 计时」这个结构记住。
这三个数管到哪儿为止
CC Switch 的后端里带「秒」的配置不止一处,混起来会很乱。把边界划清楚,这三个数的适用范围其实很窄。
它和连通性检查的 8 秒不是同一个 8。 services/stream_check.rs:50-61 里 StreamCheckConfig::default() 的 timeout_secs 也是 8,同一处还有 max_retries: 1、degraded_threshold_ms: 6000,注释写明这个超时是从旧的 45 秒降下来的。两个 8 长得一样,来路完全不同:一个是测速服务的默认,一个是连通性检查配置结构体的默认,各自独立,改一个不会带动另一个。关于 Stream Check 本身,文档口径与代码口径有几处对不上,我们另有一篇专门讲,这里不展开。
它和代理转发的那套超时更不是一回事。 代理侧的超时是落在数据库里的:src-tauri/src/database/schema.rs:131-135 给 proxy_config 表的列级 DEFAULT 是 streaming_first_byte_timeout 60、streaming_idle_timeout 120、non_streaming_timeout 600,另外还有 max_retries 3;而各应用的 seed 行又不一定取列默认值,比如 claude 那行的三个超时是 90 / 180 / 600(schema.rs:152)。这些是每个应用一行、可以改、改完落库的配置。测速这三个数则是编译进二进制的常量,不在数据库里,也不随应用区分。
两套东西连「范围」的表达方式都不同。ProxyConfig 结构体的注释(src-tauri/src/proxy/types.rs:19-27)给出的是写在注释里的建议范围:流式首字超时 1-120 秒(默认 60)、流式静默超时 60-600 秒(填 0 禁用)、非流式总超时 60-1200 秒(默认 600)。注意其中「填 0 禁用」这条——在流式转发里 0 是有意义的(response_processor.rs:700-737 里两个超时为 0 时直接不启用超时)。而在测速这边,0 不是「禁用」,是被 clamp 抬成 2。同一个 0,在两个模块里语义相反,这是最值得记一笔的地方。
它也不参与故障转移的健康判定。 代码把一条不变量写在 services/stream_check.rs:13-17(命令层在 commands/stream_check.rs:1-4 重申):连通性检查绝不触碰故障转移熔断器,熔断器只由 proxy/forwarder.rs 转发真实流量的成败被动驱动。测速服务这边我们没有读到同类的声明,但熔断这条不变量的表述本身就是排他的——它只认转发真实流量的结果。
可复现的核查动作
想自己确认上面每一句,clone 仓库后在仓库根目录做这几步:
grep -n "TIMEOUT_SECS" src-tauri/src/services/speedtest.rs
sed -n '70,90p;120,132p' src-tauri/src/services/speedtest.rs
grep -n "timeout" src-tauri/src/database/schema.rs
sed -n '15,30p' src-tauri/src/proxy/types.rs
第一条把三个常量的定义行一次列全;第二条分别对应热身请求那一段和 clamp 那一段;后两条用来比对代理侧那套完全不同的超时来源。以上为按仓库中的文件位置组合的示例命令,未经实测,行号以你 clone 到的快照为准。
判断口径也很简单:只要一个秒数是在数据库 proxy_config 表里、按应用一行的,它就跟测速无关;只要它是 speedtest.rs 顶部的 const,改它就只影响测速。
该把它调成多少
不给建议值。项目自己也没给——代码里只给了默认 8 与区间 2-30,没有任何「推荐配置」性质的文档说明这个值该怎么选。合适的数取决于你测的是什么端点、网络状况如何,这属于你自己的用法,不是本文能替你决定的。
能说清的只有「改它会牵动什么」,而且范围出乎意料地小:它只改变每次测速请求的等待上限,因而影响一个慢端点是被记成一个较大的延迟数字、还是直接被判成超时。它不改变代理转发的任何超时,不改变熔断阈值,不改变故障转移队列的选择,不改变连通性检查的行为。这几件事各有各的参数,都在别处。
什么情况说明不是这三个数的事
排查时如果出现下面这些现象,方向就不在 speedtest.rs:
- 转发中途断掉、或者长时间没有输出——那走的是流式首字超时与静默超时,配置在
proxy_config表里,和测速的 clamp 无关。按error_mapper.rs:7-32的映射规则,超时与流空闲超时对外映射成 504,这也是转发链路的行为,不是测速链路的。 - 某个供应商被跳过、请求落到了别家——那是熔断与故障转移在起作用,由转发真实流量的成败驱动,熔断参数在
proxy_config表里另有五列。 - 停止代理服务时卡住——
server.rs:233-250那里有一个独立的 5 秒等待保护,超时返回ProxyError::StopTimeout,同样与测速无关。 - 某个端点的延迟数字看起来「太好了」——回到上面第二节,那是热身之后的第二次请求,本来就不含冷启动代价。这不是异常,是设计如此。
反过来,真正指向这三个常量的情形其实只有一种:你在意的是「测一个端点最多愿意等多久」,而你给出的秒数落在 2 到 30 之外。那时你拿到的一定不是你写的那个数,而是边界值。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。