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.91k stars 1.76k forks source link

Error dialing to the given TCP address timed out #1888

Closed letniy71 closed 3 weeks ago

letniy71 commented 4 weeks ago

Hello. Errors appear dialing to the given TCP address timed out on version 1.39 and later. On version 1.38 the error did not appear

var (
    client  = &fasthttp.Client{}
)

func test(ctx context.Context, url string, timeout time.Duration) ([]byte, error) {
    req := fasthttp.AcquireRequest()

    defer fasthttp.ReleaseRequest(req)

    req.SetRequestURI(url)

    resp := fasthttp.AcquireResponse()

    if err := client.DoTimeout(req, resp, timeout); err != nil {
        if errors.Is(err, fasthttp.ErrTimeout) {
            err = askerErrors.ErrTimeout
        }

        return nil, err
    }

    .....
}
ksw2000 commented 4 weeks ago

Following your code, no errors show up in the latest version. Could you provide the full example code?

package main

import (
    "context"
    "errors"
    "fmt"
    "time"

    "github.com/valyala/fasthttp"
)

var (
    client = &fasthttp.Client{}
)

func test(ctx context.Context, url string, timeout time.Duration) ([]byte, error) {
    req := fasthttp.AcquireRequest()

    defer fasthttp.ReleaseRequest(req)

    req.SetRequestURI(url)

    resp := fasthttp.AcquireResponse()

    if err := client.DoTimeout(req, resp, timeout); err != nil {
        if errors.Is(err, fasthttp.ErrTimeout) {
            err = fmt.Errorf("askerErrors.ErrTimeout: %w", err)
        }

        return nil, err
    }

    return []byte{}, nil
}

func main() {
    fmt.Println(test(&fasthttp.RequestCtx{}, "https://www.google.com", time.Second))
}

output:

[] <nil>
erikdubbelboer commented 3 weeks ago

1.39 is more than 2 years old, please let me know if you can reproduce this in the latest version.

letniy71 commented 3 weeks ago

The problem arises if you set a timeout of around 150 milliseconds (150 * time.Millisecond). Is it possible that, starting from version 1.39, this timeout is also applied to the establishment of the TCP session? I'm testing on version v1.56.0

ksw2000 commented 3 weeks ago

You are right, I got an error when setting the timeout to less than 300ms.

func main() {
    fmt.Println(test(&fasthttp.RequestCtx{}, "https://www.google.com",time.Millisecond*300))
}
[] askerErrors.ErrTimeout: timeout

However, when setting about 350ms. There is no error.

func main() {
    fmt.Println(test(&fasthttp.RequestCtx{}, "https://www.google.com",time.Millisecond*350))
}
[] <nil>

This error might be due to an issue with the timer. We can discuss and see which part went wrong. 🤔

letniy71 commented 3 weeks ago

Is it possible to set a timeout separately for TCP? (If I use DotimeOut)

ksw2000 commented 3 weeks ago

In version 1.38, the timeout is managed using a time.Timer.

timeout := -time.Since(deadline)
// ...
go func(){
        // process request
}

// tc is time.Timer
tc := AcquireTimer(timeout)
var err error
select {
case err = <-ch:
case <-tc.C:
    mu.Lock()
    {
        if responded {
            err = <-ch
        } else {
            timedout = true
            err = ErrTimeout
        }
    }
    mu.Unlock()
}

In version 1.56, however, a timeout check is performed each step a request is processed, and an error is returned if it has timed out.

deadline := time.Time{}
timeout := req.timeout
if timeout > 0 {
    deadline = time.Now().Add(timeout)
}
// ...
for {
        // check if timeout

        // do request

        // check if timeout or exceed the maximum retry times
}

My tests in version 1.38 showed that 250ms almost always timed out, while 300ms succeeded occasionally. In version 1.56, it required 350ms. In terms of implementation, I believe the timeout mechanism in the new version is more accurate since, in the old version, the timer could cause the request to return within the timeout limit due to goroutine scheduling but still be considered as not timed out.

Nevertheless, it’s also possible that, in the new version, the delay is caused by repeatedly checking the time.