File Descriptors and Go

Ever heard of dangling pointers? Have you ever left your database connections without closing them properly?

One must pay special attention to this specially when dealing with lower level languages like Go, C, and C++.

Resource leaks really are a do or die in many applications, something very often and very easily overlooked. I personally have had applications catastrophically crash due to this. Read this to understand useful patterns and practices avoiding these problems in Go…

A true story!

Last week I saw one of my beloved Go applications crash catastrophically with such an error:

server.go:3095: http: Accept error: accept tcp [::]:7002: accept4: too many open files; retrying in 5ms

This error kept repeating for a good few minutes, until I luckily spotted the issue, and temporarily fixed it by restarting the application (Go binary as system service).

The application in question deals with a lot of incoming and outgoing HTTP requests, acting as a Facade between several services.

Due to the nature of this application, many requests of all kinds are sent, and sometimes I do not care about the response body of a performed request, only about the returned status code, or not even that.

This lead to a situation where I performed several requests ignoring the response body with the _ variable.

So please follow my advice, always, ALWAYS, close the response body of a request.

BAD

_, err := client.Do(request) if err != nil { log.Println(err.Error()) return nil }

GOOD

resp, err := client.Do(request) if err != nil { log.Println(err.Error()) return nil } defer resp.Body.Close()

If you do not close the response body, or for that matter defer its closing, the resources allocated for that will never be released, and thus your application will keep it indefinitely reserved, in Unix systems, in a file descriptor.

Keep in mind, that the default limit per application is of 1024 file descriptors. this can be manipulated, but in my opinion really should not be done for web services, unless there is an actual necessity. Keep it in the back of your head, if you need to increase this, you most certainly have a leak in your program.

The bad news is that not only HTTP requests will cause this issue. Database connections, Redis connections, Buffered readers, Actual File I/O operations…

A solid example, is indeed the database connection:

BAD

db, err := sql.Open(databaseType, connection) if err != nil { log.Println(err.Error()) panic(err.Error()) } // If you do not defer close to the connection // you will dangerously forget about doing so.

GOOD

db, err := sql.Open(databaseType, connection) if err != nil { log.Println(err.Error()) panic(err.Error()) } defer db.Close() // Use db here, no risk of forgetting to close the db connection

Anything that you have opened and forgot to close, will hog the system and bring the file descriptor count upwards, and will come back to haunt you. For that reason, you should check your system services file descriptor count sometimes. This can easily be achieved with a couple commands. Log in as root user in your Unix server and do a ps aux.

You might be wondering, Go is a Garbage Collected (GC) language, why doesn't it simply destroy these unused connections/handles/response bodies?

This simply is not the case since for the GC these handles are still "referenced" and thus are not eligible for garbage collection. They are basically dangling around, and you can avoid this bad situation with a simple defer statement.

Imagining your application is called my-application and is located at /internalApplications/my-application/app, you should filter the =ps aux=output to find:

root 531 0.0 0.1 1323436 8000 ? Ssl Nov12 0:35 /internalApplications/my-application/app

What we are intereseted in here, is the 2nd column and 11th column, which contain respectively the PID of the process and the name of the process.

To get a file descriptor count per PID, do ls /proc/{pid}/fd | wc -l, in this case you would need to do, ls /proc/531/fd | wc -l

You can take this script I made, that finds all services that are located in the /internalApplications folder and modify it, to suit your needs:

#!/bin/bash pid=( $(ps aux | grep /internalApplications/ | grep -v "grep" | awk '{print $2}') ) processName=( $(ps aux | grep /internalApplications/ | grep -v "grep" | awk '{print $11}') ) for index in ${!pid[*]}; do printf "%s --> %s --> %s\n" "${pid[$index]}" "$( ls /proc/${pid[$index]}/fd | wc -l )" "${processName[$index]}" done

Other good tips to avoid file descriptors being hogged are more related to HTTP requests. If your application is exposed to other HTTP services, ensure you follow the below tips.

Always add a request close, unless you plan to have keep-alive connections. This can easily be done:

req, err := http.NewRequestWithContext( context.Background(), "POST", "www.example.com", bytes.NewBuffer([]byte(`{"test": "example"}`)), ) if err != nil { log.Println(err.Error()) return nil, err } req.Header.Add("Content-Type", "application/json") req.Header.Add("Charset", "utf-8") // This will add a Connection: close header to the request req.Close = true // alternatively req.Header.Add("Connection", "close")

Set a default timeout for HTTP requests, to avoid hanging requests, or malicious attempts:

// Do this at the beggining of your application, in your `main.go` http.DefaultClient.Timeout = 30 * time.Second

If you use http.Server directly then make sure to set some sensible timeouts:

srv := &http.Server{ ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 100 * time.Second, } // optional disable keep alives srv.SetKeepAlivesEnabled(false)

If you use http.Client directly then also make sure to set a sensible timeout:

client := &http.Client{Timeout: 30 * time.Second}

In conclusion, you should always take special care with closing resources properly so as to not have stale sockets, and file descriptors in your application.

Always cleanup after yourself, and if possible, do a defer whatever.Close() as close to the declarations as possible, so as to not forget to do it further down the code. This also avoids resources not being closed due to early returns.

With great power, such as what Go gives developers, comes great responsability.

I will keep expanding this article as I find more useful tips for this, stay tuned!