From 0f983f046a4f489e57f392d2846401aea9add792 Mon Sep 17 00:00:00 2001 From: chick Date: Thu, 17 Sep 2026 07:21:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=E8=AF=BB=E4=B8=8A=E9=99=90?= =?UTF-8?q?=E8=A6=81=E5=AE=B9=E5=BE=97=E4=B8=8B=E6=95=B4=E6=89=B9=E8=BD=AE?= =?UTF-8?q?=E8=AF=A2=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同一类缺陷的第四处,也是最难发现的一处,因为它在最繁忙那条接缝的另一侧。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 项通过。 --- internal/transport/transport.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/transport/transport.go b/internal/transport/transport.go index 10f9ba3..4ba6a1f 100644 --- a/internal/transport/transport.go +++ b/internal/transport/transport.go @@ -35,6 +35,21 @@ const ( 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 ( // 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. @@ -142,7 +157,7 @@ func New(cfg Config) (*Client, error) { } maxBytes := cfg.MaxBytes if maxBytes <= 0 { - maxBytes = 1 << 20 + maxBytes = DefaultMaxBytes } client := &http.Client{Timeout: timeout} if cfg.RootCAs != nil {