fix: reuse stream scanner buffer in channel handlers (#5225)

This commit is contained in:
xujiantop-crypto
2026-06-05 12:18:57 +08:00
committed by GitHub
parent 01c2128e23
commit 32805849d6
9 changed files with 38 additions and 12 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ func convertCf2CompletionsRequest(textRequest dto.GeneralOpenAIRequest) *CfReque
}
func cfStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*types.NewAPIError, *dto.Usage) {
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
scanner.Split(bufio.ScanLines)
helper.SetEventStreamHeaders(c)
+4 -2
View File
@@ -1,7 +1,6 @@
package cohere
import (
"bufio"
"encoding/json"
"io"
"net/http"
@@ -86,7 +85,7 @@ func cohereStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
createdTime := common.GetTimestamp()
usage := &dto.Usage{}
responseText := ""
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
@@ -106,6 +105,9 @@ func cohereStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
data := scanner.Text()
dataChan <- data
}
if err := scanner.Err(); err != nil {
common.SysLog("error reading stream: " + err.Error())
}
stopChan <- true
}()
helper.SetEventStreamHeaders(c)
+1 -1
View File
@@ -98,7 +98,7 @@ func cozeChatHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Res
}
func cozeChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
scanner.Split(bufio.ScanLines)
helper.SetEventStreamHeaders(c)
id := helper.GetResponseID(c)
+2 -2
View File
@@ -1,7 +1,6 @@
package ollama
import (
"bufio"
"encoding/json"
"fmt"
"io"
@@ -12,6 +11,7 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/types"
@@ -397,7 +397,7 @@ func PullOllamaModelStream(baseURL, apiKey, modelName string, progressCallback f
}
// 读取流式响应
scanner := bufio.NewScanner(response.Body)
scanner := helper.NewStreamScanner(response.Body)
successful := false
for scanner.Scan() {
line := scanner.Text()
+1 -2
View File
@@ -1,7 +1,6 @@
package ollama
import (
"bufio"
"encoding/json"
"fmt"
"io"
@@ -70,7 +69,7 @@ func ollamaStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
defer service.CloseResponseBodyGracefully(resp)
helper.SetEventStreamHeaders(c)
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
usage := &dto.Usage{}
var model = info.UpstreamModelName
var responseId = common.GetUUID()
+1 -1
View File
@@ -92,7 +92,7 @@ func streamResponseTencent2OpenAI(TencentResponse *TencentChatResponse) *dto.Cha
func tencentStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
var responseText string
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
scanner.Split(bufio.ScanLines)
helper.SetEventStreamHeaders(c)
+4 -1
View File
@@ -157,7 +157,7 @@ func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*dt
func zhipuStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
var usage *dto.Usage
scanner := bufio.NewScanner(resp.Body)
scanner := helper.NewStreamScanner(resp.Body)
scanner.Split(bufio.ScanLines)
dataChan := make(chan string)
metaChan := make(chan string)
@@ -180,6 +180,9 @@ func zhipuStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.
}
}
}
if err := scanner.Err(); err != nil {
common.SysLog("error reading stream: " + err.Error())
}
stopChan <- true
}()
helper.SetEventStreamHeaders(c)
+7 -2
View File
@@ -34,6 +34,12 @@ func getScannerBufferSize() int {
return DefaultMaxScannerBufferSize
}
func NewStreamScanner(reader io.Reader) *bufio.Scanner {
scanner := bufio.NewScanner(reader)
scanner.Buffer(make([]byte, InitialScannerBufferSize), getScannerBufferSize())
return scanner
}
func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, dataHandler func(data string, sr *StreamResult)) {
if resp == nil || dataHandler == nil {
@@ -54,7 +60,7 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
var (
stopChan = make(chan bool, 3) // 增加缓冲区避免阻塞
scanner = bufio.NewScanner(resp.Body)
scanner = NewStreamScanner(resp.Body)
ticker = time.NewTicker(streamingTimeout)
pingTicker *time.Ticker
writeMutex sync.Mutex // Mutex to protect concurrent writes
@@ -104,7 +110,6 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
close(stopChan)
}()
scanner.Buffer(make([]byte, InitialScannerBufferSize), getScannerBufferSize())
scanner.Split(bufio.ScanLines)
SetEventStreamHeaders(c)
+17
View File
@@ -1,6 +1,7 @@
package helper
import (
"bufio"
"fmt"
"io"
"net/http"
@@ -81,6 +82,22 @@ func TestStreamScannerHandler_NilInputs(t *testing.T) {
StreamScannerHandler(c, &http.Response{Body: io.NopCloser(strings.NewReader(""))}, info, nil)
}
func TestNewStreamScanner_AllowsLargeStreamLine(t *testing.T) {
oldBufferMB := constant.StreamScannerMaxBufferMB
constant.StreamScannerMaxBufferMB = 1
t.Cleanup(func() {
constant.StreamScannerMaxBufferMB = oldBufferMB
})
payload := strings.Repeat("x", 128<<10)
scanner := NewStreamScanner(strings.NewReader("data: " + payload + "\n"))
scanner.Split(bufio.ScanLines)
require.True(t, scanner.Scan())
assert.Equal(t, "data: "+payload, scanner.Text())
require.NoError(t, scanner.Err())
}
func TestStreamScannerHandler_EmptyBody(t *testing.T) {
t.Parallel()