fix(gateway): WS 连接池条件式 MarkBroken 防止跨请求串流

正常终端事件(response.completed 等)退出后连接归还复用,
仅异常路径(读写错误、error 事件、客户端断连)MarkBroken 销毁。

Generate 模式:
- 引入 cleanExit 标记,仅在 isTerminalEvent break 时设置 true
- defer 中根据 cleanExit 决定是否 MarkBroken
- 所有异常路径已在各自分支中提前调用 MarkBroken

Ingress 模式:
- 引入 lastTurnClean 标记,sendAndRelay 正常完成时设为 true
- releaseSessionLease 根据 lastTurnClean 决定是否 MarkBroken
- 错误路径重置 lastTurnClean = false
- 客户端断连后 drain 仍保守 MarkBroken(L2916)
This commit is contained in:
QTom
2026-03-16 10:27:57 +08:00
parent ab4e8b2cf0
commit 3741617ebd
2 changed files with 25 additions and 5 deletions

View File

@@ -1870,7 +1870,16 @@ func (s *OpenAIGatewayService) forwardOpenAIWSV2(
}
return nil, wrapOpenAIWSFallback(classifyOpenAIWSAcquireError(err), err)
}
defer lease.Release()
// cleanExit 标记正常终端事件退出,此时上游不会再发送帧,连接可安全归还复用。
// 所有异常路径读写错误、error 事件等)已在各自分支中提前调用 MarkBroken
// 因此 defer 中只需处理正常退出时不 MarkBroken 即可。
cleanExit := false
defer func() {
if !cleanExit {
lease.MarkBroken()
}
lease.Release()
}()
connID := strings.TrimSpace(lease.ConnID())
logOpenAIWSModeDebug(
"connected account_id=%d account_type=%s transport=%s conn_id=%s conn_reused=%v conn_pick_ms=%d queue_wait_ms=%d has_previous_response_id=%v",
@@ -2248,6 +2257,7 @@ func (s *OpenAIGatewayService) forwardOpenAIWSV2(
}
if isTerminalEvent {
cleanExit = true
break
}
}
@@ -2983,12 +2993,15 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
pinnedSessionConnID = connID
}
}
// lastTurnClean 标记最后一轮 sendAndRelay 是否正常完成(收到终端事件且客户端未断连)。
// 所有异常路径读写错误、error 事件、客户端断连已在各自分支或上层L3403中 MarkBroken
// 因此 releaseSessionLease 中只需在非正常结束时 MarkBroken。
lastTurnClean := false
releaseSessionLease := func() {
if sessionLease == nil {
return
}
if dedicatedMode {
// dedicated 会话结束后主动标记损坏,确保连接不会跨会话复用。
if !lastTurnClean {
sessionLease.MarkBroken()
}
unpinSessionConn(sessionConnID)
@@ -3383,6 +3396,7 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
result, relayErr := sendAndRelay(turn, sessionLease, currentPayload, currentPayloadBytes, currentOriginalModel)
if relayErr != nil {
lastTurnClean = false
if recoverIngressPrevResponseNotFound(relayErr, turn, connID) {
continue
}
@@ -3402,6 +3416,7 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
turnRetry = 0
turnPrevRecoveryTried = false
lastTurnFinishedAt = time.Now()
lastTurnClean = true
if hooks != nil && hooks.AfterTurn != nil {
hooks.AfterTurn(turn, result, nil)
}