Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

288 строки
6.7 KiB

  1. package helper
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/QuantumNous/new-api/common"
  12. "github.com/QuantumNous/new-api/constant"
  13. "github.com/QuantumNous/new-api/logger"
  14. relaycommon "github.com/QuantumNous/new-api/relay/common"
  15. "github.com/QuantumNous/new-api/setting/operation_setting"
  16. "github.com/bytedance/gopkg/util/gopool"
  17. "github.com/gin-gonic/gin"
  18. )
  19. const (
  20. InitialScannerBufferSize = 64 << 10 // 64KB (64*1024)
  21. DefaultMaxScannerBufferSize = 64 << 20 // 64MB (64*1024*1024) default SSE buffer size
  22. DefaultStreamingTimeout = 300 * time.Second
  23. DefaultPingInterval = 10 * time.Second
  24. )
  25. func getScannerBufferSize() int {
  26. if constant.StreamScannerMaxBufferMB > 0 {
  27. return constant.StreamScannerMaxBufferMB << 20
  28. }
  29. return DefaultMaxScannerBufferSize
  30. }
  31. func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, dataHandler func(data string) bool) {
  32. if resp == nil || dataHandler == nil {
  33. return
  34. }
  35. // 确保响应体总是被关闭
  36. defer func() {
  37. if resp.Body != nil {
  38. resp.Body.Close()
  39. }
  40. }()
  41. streamingTimeout := time.Duration(constant.StreamingTimeout) * time.Second
  42. if streamingTimeout <= 0 {
  43. streamingTimeout = DefaultStreamingTimeout
  44. }
  45. var (
  46. stopChan = make(chan bool, 3) // 增加缓冲区避免阻塞
  47. scanner = bufio.NewScanner(resp.Body)
  48. ticker = time.NewTicker(streamingTimeout)
  49. pingTicker *time.Ticker
  50. writeMutex sync.Mutex // Mutex to protect concurrent writes
  51. wg sync.WaitGroup // 用于等待所有 goroutine 退出
  52. )
  53. generalSettings := operation_setting.GetGeneralSetting()
  54. pingEnabled := generalSettings.PingIntervalEnabled && !info.DisablePing
  55. pingInterval := time.Duration(generalSettings.PingIntervalSeconds) * time.Second
  56. if pingInterval <= 0 {
  57. pingInterval = DefaultPingInterval
  58. }
  59. if pingEnabled {
  60. pingTicker = time.NewTicker(pingInterval)
  61. }
  62. if common.DebugEnabled {
  63. // print timeout and ping interval for debugging
  64. println("relay timeout seconds:", common.RelayTimeout)
  65. println("relay max idle conns:", common.RelayMaxIdleConns)
  66. println("relay max idle conns per host:", common.RelayMaxIdleConnsPerHost)
  67. println("streaming timeout seconds:", int64(streamingTimeout.Seconds()))
  68. println("ping interval seconds:", int64(pingInterval.Seconds()))
  69. }
  70. // 改进资源清理,确保所有 goroutine 正确退出
  71. defer func() {
  72. // 通知所有 goroutine 停止
  73. common.SafeSendBool(stopChan, true)
  74. ticker.Stop()
  75. if pingTicker != nil {
  76. pingTicker.Stop()
  77. }
  78. // 等待所有 goroutine 退出,最多等待5秒
  79. done := make(chan struct{})
  80. gopool.Go(func() {
  81. wg.Wait()
  82. close(done)
  83. })
  84. select {
  85. case <-done:
  86. case <-time.After(5 * time.Second):
  87. logger.LogError(c, "timeout waiting for goroutines to exit")
  88. }
  89. close(stopChan)
  90. }()
  91. scanner.Buffer(make([]byte, InitialScannerBufferSize), getScannerBufferSize())
  92. scanner.Split(bufio.ScanLines)
  93. SetEventStreamHeaders(c)
  94. ctx, cancel := context.WithCancel(context.Background())
  95. defer cancel()
  96. ctx = context.WithValue(ctx, "stop_chan", stopChan)
  97. // Handle ping data sending with improved error handling
  98. if pingEnabled && pingTicker != nil {
  99. wg.Add(1)
  100. gopool.Go(func() {
  101. defer func() {
  102. wg.Done()
  103. if r := recover(); r != nil {
  104. logger.LogError(c, fmt.Sprintf("ping goroutine panic: %v", r))
  105. common.SafeSendBool(stopChan, true)
  106. }
  107. if common.DebugEnabled {
  108. println("ping goroutine exited")
  109. }
  110. }()
  111. // 添加超时保护,防止 goroutine 无限运行
  112. maxPingDuration := 30 * time.Minute // 最大 ping 持续时间
  113. pingTimeout := time.NewTimer(maxPingDuration)
  114. defer pingTimeout.Stop()
  115. for {
  116. select {
  117. case <-pingTicker.C:
  118. // 使用超时机制防止写操作阻塞
  119. done := make(chan error, 1)
  120. gopool.Go(func() {
  121. writeMutex.Lock()
  122. defer writeMutex.Unlock()
  123. done <- PingData(c)
  124. })
  125. select {
  126. case err := <-done:
  127. if err != nil {
  128. logger.LogError(c, "ping data error: "+err.Error())
  129. return
  130. }
  131. if common.DebugEnabled {
  132. println("ping data sent")
  133. }
  134. case <-time.After(10 * time.Second):
  135. logger.LogError(c, "ping data send timeout")
  136. return
  137. case <-ctx.Done():
  138. return
  139. case <-stopChan:
  140. return
  141. }
  142. case <-ctx.Done():
  143. return
  144. case <-stopChan:
  145. return
  146. case <-c.Request.Context().Done():
  147. // 监听客户端断开连接
  148. return
  149. case <-pingTimeout.C:
  150. logger.LogError(c, "ping goroutine max duration reached")
  151. return
  152. }
  153. }
  154. })
  155. }
  156. dataChan := make(chan string, 10)
  157. wg.Add(1)
  158. gopool.Go(func() {
  159. defer func() {
  160. wg.Done()
  161. if r := recover(); r != nil {
  162. logger.LogError(c, fmt.Sprintf("data handler goroutine panic: %v", r))
  163. }
  164. common.SafeSendBool(stopChan, true)
  165. }()
  166. for data := range dataChan {
  167. writeMutex.Lock()
  168. success := dataHandler(data)
  169. writeMutex.Unlock()
  170. if !success {
  171. return
  172. }
  173. }
  174. })
  175. // Scanner goroutine with improved error handling
  176. wg.Add(1)
  177. common.RelayCtxGo(ctx, func() {
  178. defer func() {
  179. close(dataChan)
  180. wg.Done()
  181. if r := recover(); r != nil {
  182. logger.LogError(c, fmt.Sprintf("scanner goroutine panic: %v", r))
  183. }
  184. common.SafeSendBool(stopChan, true)
  185. if common.DebugEnabled {
  186. println("scanner goroutine exited")
  187. }
  188. }()
  189. for scanner.Scan() {
  190. // 检查是否需要停止
  191. select {
  192. case <-stopChan:
  193. return
  194. case <-ctx.Done():
  195. return
  196. case <-c.Request.Context().Done():
  197. return
  198. default:
  199. }
  200. ticker.Reset(streamingTimeout)
  201. data := scanner.Text()
  202. if common.DebugEnabled {
  203. println(data)
  204. }
  205. if len(data) < 6 {
  206. continue
  207. }
  208. if data[:5] != "data:" && data[:6] != "[DONE]" {
  209. continue
  210. }
  211. data = data[5:]
  212. data = strings.TrimSpace(data)
  213. if data == "" {
  214. continue
  215. }
  216. if !strings.HasPrefix(data, "[DONE]") {
  217. info.SetFirstResponseTime()
  218. info.ReceivedResponseCount++
  219. select {
  220. case dataChan <- data:
  221. case <-ctx.Done():
  222. return
  223. case <-stopChan:
  224. return
  225. }
  226. } else {
  227. // done, 处理完成标志,直接退出停止读取剩余数据防止出错
  228. if common.DebugEnabled {
  229. println("received [DONE], stopping scanner")
  230. }
  231. return
  232. }
  233. }
  234. if err := scanner.Err(); err != nil {
  235. if err != io.EOF {
  236. logger.LogError(c, "scanner error: "+err.Error())
  237. }
  238. }
  239. })
  240. // 主循环等待完成或超时
  241. select {
  242. case <-ticker.C:
  243. // 超时处理逻辑
  244. logger.LogError(c, "streaming timeout")
  245. case <-stopChan:
  246. // 正常结束
  247. logger.LogInfo(c, "streaming finished")
  248. case <-c.Request.Context().Done():
  249. // 客户端断开连接
  250. logger.LogInfo(c, "client disconnected")
  251. }
  252. }