How does multithreading affect http keep-alive connection?
Asked Answered
M

2

8
var (
    httpClient *http.Client
)

const (
    MaxIdleConnections int = 20
    RequestTimeout     int = 5
)

// init HTTPClient
func init() {
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConnsPerHost: MaxIdleConnections,
        },
        Timeout: time.Duration(RequestTimeout) * time.Second,
    }

    return client
}

func makeRequest() {
    var endPoint string = "https://localhost:8080/doSomething"

    req, err := http.NewRequest("GET", ....)
    
    response, err := httpClient.Do(req)
    if err != nil && response == nil {
        log.Fatalf("Error sending request to API endpoint. %+v", err)
    } else {
        // Close the connection to reuse it
        defer response.Body.Close()
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            log.Fatalf("Couldn't parse response body. %+v", err)
        }
        
        log.Println("Response Body:", string(body))
    }
}

I have the following code in Go. Go uses http-keep-alive connection. Thus, from my understanding, httpClient.Do(req) will not create a new connection, since golang uses default persistent connections.

  1. From my understanding HTTP persistent connection makes one request at a time, ie the second request can only be made after first response. However if multiple threads call makeRequest() what will happen ? Will httpClient.Do(req) send another request even before previous one got a response ?

  2. I assume server times-out any keep-alive connection made by client. If server were to timeout, then the next time httpClient.Do(req)is called, would it establish a new connection ?

Mcknight answered 6/9, 2020 at 2:18 Comment(6)
See the documentation for godoc.org/net/http#Transport.MaxConnsPerHostRivkarivkah
your current code will not compile. How is init able to returning something?Marquettamarquette
"The second request can only be made after first response." No. If all existing connections are still in use the client will open another connection, up to some configured maximum. Only if this limit is reached will new requests have to wait for a connection to become idle.Wheelsman
@Wheelsman I am not using any connection pool. I am only relying on Client.Do to open a new connection. So are you suggesting Client.Do will open a new connection although go lang uses keep-alive ?Mcknight
You're using the http.Transport, which implements a connection pool. "By default, Transport caches connections for future re-use."Wheelsman
For (1) there is also HTTP pipelining for HTTP/1.1 and multiplexing for HTTP/2. Those allow multiple concurrent requests on the same connection. It doesn't change the answer, but it's good to know.Robbie
C
7

an http.Client has a Transport to which it delegates a lot of the low-level details of making a request. You can change pretty much anything by giving your client a custom Transport. The rest of this answer will largely assume that you're using http.DefaultClient or at least a client with http.DefaultTransport.

When making a new request, if an idle connection to the appropriate server is available, the transport will use it.

If no idle connection is available (because there never was one, or because other goroutines are using them all, or because the server closed the connection, or there was some other error) then the transport will consider making a new connection, limited by MaxConnsPerHost (default: no limit). If MaxConnsPerHost would be exceeded, then the request will block until an existing request completes and a connection becomes available. Otherwise, a new connection will be made for this request.

On completion of a request, the client will cache the connection for later use (limited by MaxIdleConns and MaxIdleConnsPerHost; DefaultTransport has a limit of 100 idle connections globally, and no limit per-host).

Idle connections will be closed after IdleConnTimeout if they aren't used to make a request; for DefaultTransport the limit is 90 seconds.

All of which means that by default, Go will make enough connections to satisfy parallelism (up to certain limits which you can adjust) but it will also reuse keep-alive connections as much as possible by maintaining a pool of idle connections for some length of time.

Ceiba answered 8/9, 2020 at 2:49 Comment(2)
Thanks for answer. Assuming there is only 1 connection in connection pool, and that one connection is keep alive, what happens when multiple threads try to use that one connection ? Does it block till previous request is done with ? keep-alive is not the same as pipelining.Mcknight
@Mcknight that's in my answer.Ceiba
P
1

It will not affect the http keep-alive connection, base on your code, you are using global httpClient, this will not create a new connection if called in multiple thread as you expected, Also it read the response.Body before it closed. If the provided response.Body is an io.Closer, it will closed after the request.

Postwar answered 8/9, 2020 at 15:9 Comment(2)
yes but if multiple threads call the same connection, will it block till first one responds ?Mcknight
According to httpClient.Do(re *Request) document. The url.Error value's timeout method will report true if request time out or was canceled. Request connection caches for future re-use and the behavior will managed using Transport's CloseIdleConnections method, MaxIdleConnsPerHost and DisableKeepAlives fields based on Tranport documentation. For me, It will cache the connection request till first one respond.Postwar

© 2022 - 2024 — McMap. All rights reserved.