Files
team-api/relay/common/stream_status.go
T
2026-05-10 12:21:27 +08:00

110 lines
2.8 KiB
Go

package common
import (
"sync"
)
// StreamEndReason 流式响应结束原因
type StreamEndReason string
const (
StreamEndReasonDone StreamEndReason = "done"
StreamEndReasonTimeout StreamEndReason = "timeout"
StreamEndReasonClientGone StreamEndReason = "client_gone"
StreamEndReasonError StreamEndReason = "error"
StreamEndReasonPanic StreamEndReason = "panic"
StreamEndReasonHandlerStop StreamEndReason = "handler_stop" // dataHandler 调用了 Stop()
StreamEndReasonPingFail StreamEndReason = "ping_fail" // ping 写入失败
StreamEndReasonScannerErr StreamEndReason = "scanner_err" // bufio.Scanner 错误
StreamEndReasonEOF StreamEndReason = "eof" // scanner EOF 未收到 [DONE]
)
const maxStreamErrors = 20
// StreamStatus 流式响应状态追踪
type StreamStatus struct {
mu sync.RWMutex
endOnce sync.Once
reason StreamEndReason
err error
errors []string
errCount int
}
// NewStreamStatus 创建新的流式状态追踪器
func NewStreamStatus() *StreamStatus {
return &StreamStatus{}
}
// SetEndReason 设置流结束原因(first-writer-wins,仅第一次调用生效)
func (s *StreamStatus) SetEndReason(reason StreamEndReason, err error) {
s.endOnce.Do(func() {
s.mu.Lock()
s.reason = reason
s.err = err
s.mu.Unlock()
})
}
// GetEndReason 获取流结束原因
func (s *StreamStatus) GetEndReason() StreamEndReason {
s.mu.RLock()
defer s.mu.RUnlock()
return s.reason
}
// RecordError 记录软错误(流继续处理)
func (s *StreamStatus) RecordError(msg string) {
s.mu.Lock()
defer s.mu.Unlock()
s.errCount++
if len(s.errors) < maxStreamErrors {
s.errors = append(s.errors, msg)
}
}
// ErrorCount 获取错误计数
func (s *StreamStatus) ErrorCount() int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.errCount
}
// IsPartialStreamEnd 流已开始传输但未正常结束,应按已消费 usage 部分结算。
func (s *StreamStatus) IsPartialStreamEnd() bool {
r := s.GetEndReason()
return r == StreamEndReasonClientGone || r == StreamEndReasonScannerErr ||
r == StreamEndReasonTimeout || r == StreamEndReasonPingFail
}
// IsNormalEnd 是否正常结束
func (s *StreamStatus) IsNormalEnd() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.reason == StreamEndReasonDone || s.reason == StreamEndReasonHandlerStop || s.reason == ""
}
// HasErrors 是否有错误
func (s *StreamStatus) HasErrors() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.reason == StreamEndReasonError || s.reason == StreamEndReasonPanic || s.errCount > 0
}
// Error 获取错误信息
func (s *StreamStatus) Error() error {
s.mu.RLock()
defer s.mu.RUnlock()
return s.err
}
// Summary 返回状态摘要
func (s *StreamStatus) Summary() string {
s.mu.RLock()
defer s.mu.RUnlock()
if s.err != nil {
return string(s.reason) + ": " + s.err.Error()
}
return string(s.reason)
}