net/http server: too many open files error

As Martin say in comment I don't really closed the Body after the Get request. I used defer res.Body.Close() but it's not executed since I'm staying in the for loop. So continue dont't trigger defer


Please note that in some cases the setting in /etc/sysctl.conf net.ipv4.tcp_tw_recycle = 1

Could cause this error because TCP connections remain open.


I found a post explaining the root problem in a lot more detail. Nathan Smith even explains how to control timeouts on the TCP level, if needed. Below is a summary of everything I could find on this particular problem, as well as the best practices to avoid this problem in future.

Problem

When a response is received regardless of whether response-body is required or not, the connection is kept alive until the response-body stream is closed. So, as mentioned in this thread, always close the response-body. Even if you do not need to use/read the body content:

func Ping(url string) (bool) {
    // simple GET request on given URL
    res, err := http.Get(url)
    if err != nil {
        // if unable to GET given URL, then ping must fail
        return false
    }

    // always close the response-body, even if content is not required
    defer res.Body.Close()

    // is the page status okay?
    return res.StatusCode == http.StatusOK
}

Best Practice

As mentioned by Nathan Smith never use the http.DefaultClient in production systems, this includes calls like http.Get as it uses http.DefaultClient at its base.

Another reason to avoid http.DefaultClient is that it is a Singleton (package level variable), meaning that the garbage collector will not try to clean it up, which will leave idling subsequent streams/sockets alive.

Instead create your own instance of http.Client and remember to always specify a sane Timeout:

func Ping(url string) (bool) {
    // create a new instance of http client struct, with a timeout of 2sec
    client := http.Client{ Timeout: time.Second * 2 }

    // simple GET request on given URL
    res, err := client.Get(url)
    if err != nil {
        // if unable to GET given URL, then ping must fail
        return false
    }

    // always close the response-body, even if content is not required
    defer res.Body.Close()

    // is the page status okay?
    return res.StatusCode == http.StatusOK
}

Safety Net

The safety net is for that newbie on the team, who does not know the shortfalls of http.DefaultClient usage. Or even that very useful, but not so active, open-source library that is still riddled with http.DefaultClient calls.

Since http.DefaultClient is a Singleton we can easily change the Timeout setting, just to ensure that legacy code does not cause idle connections to remain open.

I find it best to set this on the package main file in the init function:

package main

import (
    "net/http"
    "time"
)

func init() {
    /*
    Safety net for 'too many open files' issue on legacy code.
    Set a sane timeout duration for the http.DefaultClient, to ensure idle connections are terminated.
    Reference: https://stackoverflow.com/questions/37454236/net-http-server-too-many-open-files-error
    */
    http.DefaultClient.Timeout = time.Minute * 10
}

Tags:

Go