| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- package service
- import (
- "encoding/json"
- "fmt"
- "runtime"
- "strings"
- "testing"
- "github.com/stretchr/testify/require"
- )
- // buildReplayTurnPayload 构造第 turn 轮的全量重发 payload:客户端历史逐轮追加
- // itemBytes 大小的项(Codex store=false 模式的真实形态)。
- func buildReplayTurnPayload(turn, itemBytes int) []byte {
- var b strings.Builder
- _, _ = b.WriteString(`{"type":"response.create","model":"gpt-5.5","stream":true,"input":[`)
- filler := strings.Repeat("x", itemBytes)
- for i := 1; i <= turn; i++ {
- if i > 1 {
- _, _ = b.WriteString(",")
- }
- _, _ = fmt.Fprintf(&b, `{"type":"input_text","text":"turn-%d-%s"}`, i, filler)
- }
- _, _ = b.WriteString(`]}`)
- return []byte(b.String())
- }
- // TestOpenAIWSReplayStateBuildAllocationBounded 长会话分配回归:128 turn、每轮
- // ~10KiB 增量,全量历史逐轮重发。replay 状态构建(build + 历史保存)必须共享
- // 正文而非逐轮深拷贝,否则累计分配为 O(T²)(本场景 >160MB);共享实现的累计
- // 分配与客户端重发总量同阶(~85MB payload 本身之外仅头数组与解析开销)。
- func TestOpenAIWSReplayStateBuildAllocationBounded(t *testing.T) {
- const (
- turns = 128
- itemBytes = 10 * 1024
- )
- payloads := make([][]byte, 0, turns)
- for turn := 1; turn <= turns; turn++ {
- payloads = append(payloads, buildReplayTurnPayload(turn, itemBytes))
- }
- var history []json.RawMessage
- historyExists := false
- delta := []json.RawMessage{json.RawMessage(`{"type":"function_call","id":"item_1","call_id":"call_1","name":"exec","arguments":"{}"}`)}
- runtime.GC()
- var before runtime.MemStats
- runtime.ReadMemStats(&before)
- for turn := 1; turn <= turns; turn++ {
- items, exists, err := buildOpenAIWSReplayInputSequence(history, historyExists, payloads[turn-1], turn > 1)
- require.NoError(t, err)
- require.True(t, exists)
- require.Len(t, items, turn)
- // 保存历史 + collector 增量合并(与 ingress/bridge 保存点同构)。
- history = combineOpenAIWSReplayItems(items, delta)
- historyExists = true
- // 下一轮 payload 含全部用户项但不含 collector 项,触发 sanitize+merge 路径中
- // 最常见的 prefix 分支比较。
- history = history[:len(history)-1]
- }
- var after runtime.MemStats
- runtime.ReadMemStats(&after)
- allocated := after.TotalAlloc - before.TotalAlloc
- // 正文共享实现全程约 2.5MB(头数组与 gjson 解析开销)。任何逐轮 O(H) 的
- // 字节级复制(extract 拷贝、正文 clone、保存点深拷贝)都会叠加 ≥84MB
- // (sum(t×10KiB))的量级,直接击穿此上界。
- const maxAllocatedBytes = 32 * 1024 * 1024
- require.Lessf(
- t,
- allocated,
- uint64(maxAllocatedBytes),
- "replay 状态构建累计分配 %d bytes 超出上界,疑似回归为逐轮深拷贝",
- allocated,
- )
- }
|