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:
cneicy committed 2026-09-26 04:49:11 +08:00
1 parent aa6c3f50f4
commit ceddfd3d6b
828 files changed
+122253 -13702

No files matched your search

+46 -12
View File
@@ -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 {