feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
@@ -30,7 +30,8 @@ const (
|
||||
// unconditional wg.Wait() in cleanup can always finish. Without it, a slow
|
||||
// but connected client (full TCP buffer, no server WriteTimeout) could hang
|
||||
// the handler forever.
|
||||
streamWriteTimeout = 30 * time.Second
|
||||
streamWriteTimeout = 30 * time.Second
|
||||
clientGoneDrainTimeout = 2 * time.Minute
|
||||
)
|
||||
|
||||
func getScannerBufferSize() int {
|
||||
@@ -40,9 +41,15 @@ func getScannerBufferSize() int {
|
||||
return DefaultMaxScannerBufferSize
|
||||
}
|
||||
|
||||
func NewStreamScanner(reader io.Reader) *bufio.Scanner {
|
||||
// NewStreamScanner shares relay scanner configuration. Callers buffering bounded
|
||||
// task state may additionally cap a line without increasing the configured limit.
|
||||
func NewStreamScanner(reader io.Reader, maxBytes ...int) *bufio.Scanner {
|
||||
limit := getScannerBufferSize()
|
||||
if len(maxBytes) > 0 && maxBytes[0] > 0 {
|
||||
limit = min(limit, maxBytes[0])
|
||||
}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Buffer(make([]byte, InitialScannerBufferSize), getScannerBufferSize())
|
||||
scanner.Buffer(make([]byte, min(InitialScannerBufferSize, limit)), limit)
|
||||
return scanner
|
||||
}
|
||||
|
||||
@@ -121,11 +128,11 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
|
||||
logger.LogDebug(c, "streaming timeout seconds: %d", int64(streamingTimeout.Seconds()))
|
||||
logger.LogDebug(c, "ping interval seconds: %d", int64(pingInterval.Seconds()))
|
||||
|
||||
cleanup := func() {
|
||||
cleanup := func(closeUpstream bool) {
|
||||
cleanupOnce.Do(func() {
|
||||
cancel()
|
||||
stop()
|
||||
if resp.Body != nil {
|
||||
if closeUpstream && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
@@ -138,7 +145,9 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
|
||||
})
|
||||
}
|
||||
// Ensure gin.Context is not returned to Gin's pool while any stream goroutine can still use it.
|
||||
defer cleanup()
|
||||
defer func() {
|
||||
cleanup(true)
|
||||
}()
|
||||
|
||||
scanner.Split(bufio.ScanLines)
|
||||
copyCodexSSEHeaders(c, resp)
|
||||
@@ -289,19 +298,44 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
|
||||
info.StreamStatus.SetEndReason(relaycommon.StreamEndReasonEOF, nil)
|
||||
})
|
||||
|
||||
// 客户端断开后仍读取上游终止事件中的 usage,避免上游已计费而
|
||||
// NewAPI 无法结算。各写入函数会自行跳过已断开的客户端。
|
||||
drainClientGone := func(err error) {
|
||||
info.StreamStatus.SetClientGone(err)
|
||||
logger.LogInfo(c, "client disconnected; draining upstream for billing usage")
|
||||
drainTimer := time.NewTimer(clientGoneDrainTimeout)
|
||||
select {
|
||||
case <-stopChan:
|
||||
case <-drainTimer.C:
|
||||
logger.LogError(c, "client_gone upstream drain timeout; usage may be unavailable")
|
||||
}
|
||||
if !drainTimer.Stop() {
|
||||
select {
|
||||
case <-drainTimer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主循环等待完成或超时
|
||||
select {
|
||||
case <-ticker.C:
|
||||
info.StreamStatus.SetEndReason(relaycommon.StreamEndReasonTimeout, nil)
|
||||
if err := c.Request.Context().Err(); err != nil {
|
||||
drainClientGone(err)
|
||||
} else {
|
||||
info.StreamStatus.SetEndReason(relaycommon.StreamEndReasonTimeout, nil)
|
||||
}
|
||||
case <-stopChan:
|
||||
// EndReason already set by the goroutine that triggered stopChan
|
||||
// A client disconnect can race with upstream [DONE]. Preserve it as the
|
||||
// diagnostic reason while keeping fatal handler/scanner reasons intact.
|
||||
if err := c.Request.Context().Err(); err != nil {
|
||||
info.StreamStatus.SetClientGone(err)
|
||||
}
|
||||
case <-c.Request.Context().Done():
|
||||
// 客户端断开:立即 cleanup 关闭上游 resp.Body,解除 scanner 阻塞并让上游停止生成,
|
||||
// 避免为已放弃的请求继续消费上游 token。
|
||||
info.StreamStatus.SetEndReason(relaycommon.StreamEndReasonClientGone, c.Request.Context().Err())
|
||||
drainClientGone(c.Request.Context().Err())
|
||||
}
|
||||
|
||||
cleanup()
|
||||
cleanup(true)
|
||||
if info.StreamStatus.IsNormalEnd() && !info.StreamStatus.HasErrors() {
|
||||
logger.LogInfo(c, fmt.Sprintf("stream ended: %s", info.StreamStatus.Summary()))
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user