Files
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

327 lines
7.7 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpserver
import (
"bufio"
"bytes"
"context"
"fmt"
"net"
"net/http"
"strconv"
"sync"
"time"
"github.com/gin-gonic/gin"
mhttp "github.com/milvus-io/milvus/internal/http"
"github.com/milvus-io/milvus/internal/json"
"github.com/milvus-io/milvus/pkg/v3/mlog"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
)
// BufferPool represents a pool of buffers.
type BufferPool struct {
pool sync.Pool
}
// Get returns a buffer from the buffer pool.
// If the pool is empty, a new buffer is created and returned.
func (p *BufferPool) Get() *bytes.Buffer {
buf := p.pool.Get()
if buf == nil {
return &bytes.Buffer{}
}
return buf.(*bytes.Buffer)
}
// Put adds a buffer back to the pool.
func (p *BufferPool) Put(buf *bytes.Buffer) {
p.pool.Put(buf)
}
// Timeout struct
type Timeout struct {
handler gin.HandlerFunc
}
const timeoutRecorderNoWritten = -1
type timeoutResponseRecorder struct {
body *bytes.Buffer
headers http.Header
mu sync.Mutex
closed bool
status int
size int
closeNotify chan bool
}
func newTimeoutResponseRecorder(buf *bytes.Buffer) *timeoutResponseRecorder {
return &timeoutResponseRecorder{
body: buf,
headers: make(http.Header),
status: http.StatusOK,
size: timeoutRecorderNoWritten,
closeNotify: make(chan bool),
}
}
func (w *timeoutResponseRecorder) Header() http.Header {
return w.headers
}
func (w *timeoutResponseRecorder) Write(data []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed || w.body == nil {
return 0, merr.WrapErrServiceInternalMsg("response writer closed")
}
if !w.written() {
w.size = 0
}
n, err := w.body.Write(data)
w.size += n
return n, err
}
func (w *timeoutResponseRecorder) WriteString(s string) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed || w.body == nil {
return 0, merr.WrapErrServiceInternalMsg("response writer closed")
}
if !w.written() {
w.size = 0
}
n, err := w.body.WriteString(s)
w.size += n
return n, err
}
func (w *timeoutResponseRecorder) WriteHeader(code int) {
if code == -1 {
return
}
checkWriteHeaderCode(code)
w.mu.Lock()
defer w.mu.Unlock()
if w.closed || w.written() {
return
}
w.status = code
}
func (w *timeoutResponseRecorder) WriteHeaderNow() {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed || w.written() {
return
}
w.size = 0
}
func (w *timeoutResponseRecorder) Status() int {
w.mu.Lock()
defer w.mu.Unlock()
return w.status
}
func (w *timeoutResponseRecorder) Size() int {
w.mu.Lock()
defer w.mu.Unlock()
return w.size
}
func (w *timeoutResponseRecorder) Written() bool {
w.mu.Lock()
defer w.mu.Unlock()
return w.written()
}
func (w *timeoutResponseRecorder) Flush() {}
func (w *timeoutResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, merr.WrapErrServiceInternalMsg("response writer does not support hijack")
}
func (w *timeoutResponseRecorder) CloseNotify() <-chan bool {
return w.closeNotify
}
func (w *timeoutResponseRecorder) Pusher() http.Pusher {
return nil
}
func (w *timeoutResponseRecorder) CommitTo(realWriter gin.ResponseWriter) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed || w.body == nil {
return merr.WrapErrServiceInternalMsg("response writer closed")
}
dst := realWriter.Header()
for k, vv := range w.headers {
dst[k] = append([]string(nil), vv...)
}
realWriter.WriteHeader(w.status)
if w.body.Len() == 0 {
if w.written() || w.status != http.StatusOK {
realWriter.WriteHeaderNow()
}
return nil
}
_, err := realWriter.Write(w.body.Bytes())
return err
}
func (w *timeoutResponseRecorder) Close() {
w.mu.Lock()
defer w.mu.Unlock()
if w.body != nil {
w.body.Reset()
w.body = nil
}
w.closed = true
}
func (w *timeoutResponseRecorder) CloseForTimeout() {
w.Close()
}
func (w *timeoutResponseRecorder) written() bool {
return w.size != timeoutRecorderNoWritten
}
func checkWriteHeaderCode(code int) {
if code < 100 || code > 999 {
panic(fmt.Sprintf("invalid http status code: %d", code))
}
}
var timeoutContextKeysToPropagate = []string{
HTTPReturnCode,
HTTPReturnMessage,
ContextRequest,
ContextResponse,
"traceID",
}
func propagateTimeoutContextKeys(dst *gin.Context, src *gin.Context) {
for _, key := range timeoutContextKeysToPropagate {
if value, ok := src.Get(key); ok {
dst.Set(key, value)
}
}
}
func timeoutMiddleware(handler gin.HandlerFunc) gin.HandlerFunc {
timeoutHandler := &Timeout{
handler: handler,
}
bufPool := &BufferPool{}
return func(gCtx *gin.Context) {
timeout := paramtable.Get().HTTPCfg.RequestTimeoutMs.GetAsDuration(time.Millisecond)
requestTimeout := gCtx.Request.Header.Get(mhttp.HTTPHeaderRequestTimeout)
if requestTimeout != "" {
timeoutSecond, err := strconv.ParseInt(requestTimeout, 10, 64)
if err != nil {
HTTPAbortReturn(gCtx, http.StatusOK, gin.H{
mhttp.HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
mhttp.HTTPReturnMessage: merr.WrapErrParameterInvalidMsg(
"%s parse failed, err: %s",
mhttp.HTTPHeaderRequestTimeout,
err.Error(),
).Error(),
})
return
}
timeout = time.Duration(timeoutSecond) * time.Second
}
topCtx, cancel := context.WithTimeout(gCtx.Request.Context(), timeout)
defer cancel()
req := gCtx.Request.WithContext(topCtx)
gCtx.Request = req
finish := make(chan struct{}, 1)
panicChan := make(chan interface{}, 1)
realWriter := gCtx.Writer
buffer := bufPool.Get()
buffer.Reset()
recorder := newTimeoutResponseRecorder(buffer)
handlerCtx := gCtx.Copy()
handlerCtx.Request = req
handlerCtx.Writer = recorder
go func() {
defer func() {
if p := recover(); p != nil {
panicChan <- p
}
}()
timeoutHandler.handler(handlerCtx)
finish <- struct{}{}
}()
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case p := <-panicChan:
recorder.Close()
bufPool.Put(buffer)
gCtx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{mhttp.HTTPReturnCode: http.StatusInternalServerError})
panic(p)
case <-finish:
propagateTimeoutContextKeys(gCtx, handlerCtx)
if handlerCtx.IsAborted() {
gCtx.Abort()
}
gCtx.Next()
if err := recorder.CommitTo(realWriter); err != nil {
mlog.Warn(context.TODO(), "failed to write response body", mlog.Err(err))
recorder.Close()
bufPool.Put(buffer)
return
}
recorder.Close()
bufPool.Put(buffer)
case <-timer.C:
cancel()
gCtx.Abort()
gCtx.Set(HTTPReturnCode, merr.TimeoutCode)
gCtx.Set(HTTPReturnMessage, "request timeout")
recorder.CloseForTimeout()
bufPool.Put(buffer)
realWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
if traceID, ok := getTraceID(gCtx); ok {
setTraceIDHeaderTo(realWriter.Header(), traceID)
}
realWriter.WriteHeader(http.StatusRequestTimeout)
body, _ := json.Marshal(gin.H{HTTPReturnCode: merr.TimeoutCode, HTTPReturnMessage: "request timeout"})
realWriter.Write(body)
}
}
}