fix(agent): 读上限要容得下整批轮询结果

同一类缺陷的第四处,也是最难发现的一处,因为它在最繁忙那条接缝的另一侧。agent
限制自己从中继读多少,而那个上限必须容得下中继**合法能发**的最大东西:一整批
轮询结果,即每个邮箱槽一个信封。原值 1 MiB 是"给宽一点"拍出来的,没有任何推导;
而按策略上限算,满邮箱的一批约 4 MiB。于是 agent 会拒掉一个中继答得好好的轮询,
然后重试进同一个拒绝——表现为持续的连接抖动,日志说"relay response exceeds
1048576 bytes",而中继那边一切正常。

现在取 8 MiB,并把推导写清楚:中继信封上限 × 邮箱深度,留一档余量。两侧的关系由
test/limits.test.js 钉住(常量从 Go 源码读出),因为这是跨语言、跨进程的一对数字,
任何一侧单独都看不到。

变异验证:改回 1 MiB,测试失败。

Node 119 项、Go 全包、Swift 101 项通过。
This commit is contained in:
chick
2026-09-17 07:21:29 +08:00
parent 274e25961a
commit 0f983f046a
+16 -1
View File
@@ -35,6 +35,21 @@ const (
PathDevices = "/v1/agent/devices" PathDevices = "/v1/agent/devices"
) )
// DefaultMaxBytes bounds a relay response body.
//
// It has to admit a full long-poll batch, which is the largest thing the relay can legitimately send:
// one envelope per mailbox slot. The previous 1 MiB was chosen as "generous" without being derived
// from anything, and it is smaller than what the relay is now allowed to send — a mailbox of eight
// envelopes at the policy's ceiling is about 4 MiB, so a full batch would have been refused as
// "relay response exceeds 1048576 bytes", and the agent would have retried a poll the relay answered
// correctly. That is the same class of defect as the relay's own envelope ceiling: a bound picked by
// feel, on the other side of a seam, disagreeing with the party that produces the data.
//
// The derivation is stated here rather than computed from the policy because the agent reads the
// policy for its own limits; the two numbers are pinned against each other by
// test/limits.test.js, which is where a change on either side becomes visible.
const DefaultMaxBytes int64 = 8 << 20
var ( var (
// ErrSessionExpired means the relay no longer recognises this session. The caller re-runs the // ErrSessionExpired means the relay no longer recognises this session. The caller re-runs the
// handshake and polls again; it is not an error worth crashing over. // handshake and polls again; it is not an error worth crashing over.
@@ -142,7 +157,7 @@ func New(cfg Config) (*Client, error) {
} }
maxBytes := cfg.MaxBytes maxBytes := cfg.MaxBytes
if maxBytes <= 0 { if maxBytes <= 0 {
maxBytes = 1 << 20 maxBytes = DefaultMaxBytes
} }
client := &http.Client{Timeout: timeout} client := &http.Client{Timeout: timeout}
if cfg.RootCAs != nil { if cfg.RootCAs != nil {