valyala / fasthttp

Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
MIT License
21.94k stars 1.76k forks source link

The latest fasthttp client DoTimeout didn't meet exceptation #1619

Closed AprilYoLies closed 1 year ago

AprilYoLies commented 1 year ago

server.go

package main

import (
    "flag"
    "github.com/valyala/fasthttp"
    "log"
    "time"
)

var (
    addr = flag.String("addr", "127.0.0.1:28080", "TCP address to listen to")
)

func main() {
    flag.Parse()
    h := requestHandler
    if err := fasthttp.ListenAndServe(*addr, h); err != nil {
        log.Fatalf("Error in ListenAndServe: %v", err)
    }
}

func requestHandler(ctx *fasthttp.RequestCtx) {
    time.Sleep(time.Second * 20)
    ctx.SetContentType("text/plain; charset=utf8")
    ctx.Response.SetStatusCode(200)
}

client.go

package main

import (
    "fmt"
    "net"
    "time"

    "github.com/valyala/fasthttp"
)

var client *fasthttp.Client

func main() {
    tcpDialer := &fasthttp.TCPDialer{
        Concurrency: 4096,
    }
    client = &fasthttp.Client{
        MaxIdleConnDuration: 10 * time.Second,
        MaxConnsPerHost:     10240,
        ReadTimeout:         2 * time.Second,
        WriteTimeout:        time.Duration(30) * time.Second,
        ReadBufferSize:      6 * 1024,
        Dial: func(addr string) (net.Conn, error) {
            return tcpDialer.DialTimeout(addr, 2*time.Second)
        },
    }
    for i := 0; i < 1; i++ {
        go sendGetRequest()
    }
    select {}
}

func sendGetRequest() {
    for {
        req := fasthttp.AcquireRequest()
        resp := fasthttp.AcquireResponse()
        req.SetRequestURI("http://127.0.0.1:28080/")
        req.Header.SetMethod(fasthttp.MethodGet)
        fmt.Printf("start at %d\n", time.Now().Second())
        err := client.DoTimeout(req, resp, time.Millisecond*200)
        fmt.Printf("stop at %d\n", time.Now().Second())
        if err == nil {
            fmt.Printf("DEBUG Response: %d\n", resp.StatusCode())
        } else {
            fmt.Printf("ERR Connection error: %v\n", err)
        }
        fasthttp.ReleaseRequest(req)
        fasthttp.ReleaseResponse(resp)
    }
}

And I hope client.DoTimeout method can return in 200ms, however it return in one second. If I given the timeout with 400ms, client.DoTimeout method will return in two second.

erikdubbelboer commented 1 year ago

This was fixed in https://github.com/valyala/fasthttp/commit/8cc5539af71f1f944d81365c7bccc2958ded0a7f I'll tag a release for this tomorrow.

zxpdmw commented 1 year ago

By default, the internal logic of fasthttp retries five times. Therefore, the result takes five times as long as the timeout period.

    client = &fasthttp.Client{
        MaxIdemponentCallAttempts: 1, 
        MaxIdleConnDuration:       10 * time.Second,
        MaxConnsPerHost:           10240,
        ReadTimeout:               2 * time.Second,
        WriteTimeout:              time.Duration(30) * time.Second,
        ReadBufferSize:            6 * 1024,
        Dial: func(addr string) (net.Conn, error) {
            return tcpDialer.DialTimeout(addr, 2*time.Second)
        },
    }

Use this variable MaxIdemponentCallAttempts: 1 to control the number of retries, and if you set the number of retries to 1, the result is as expected.