Mom’s Grinder and the Dim Lights! Avoiding Leaks in Go

What Are Resource Leaks in Go?

suyog shinde
3 min readNov 7, 2024

In Go, a resource leak occurs when your program fails to properly release system resources it no longer needs. Which further leads unnecessary consumption of resources like (Memory, Cpu)

I did’t find any more simple example than below to explain a resource leak in real life.

Mom’s Grinder and the Dim Lights

Imagine your mom wants to use the grinder to make something delicious. However, whenever she turns on the grinder, the lights in the kitchen dim a bit. She notices this and tells you, “Turn off the fan and some lights so there’s enough power for the grinder.”

Now, if you forget to turn off those lights or the fan, the grinder won’t get enough power, and it won’t work efficiently.

So, Not turning off the resources is increasing the electricity bill, Affecting other appliances, and many more.

Moral of the story, Make the resources free when not in need and Always listen to Mom.

In programming,

  • Memory: Not freeing memory when it’s no longer required.
  • File Descriptors: Failing to close files after use.
  • Network Connections: Leaving open sockets or HTTP connections.
  • Database Connections: Forgetting to release connections to a database pool.
  • Goroutines: Unintentionally leaving goroutines running, leading to excessive consumption of memory and CPU.

Resource Lifecycle

Resource Leaks are happens mainly if you don’t do Resource Cleanup

Garbage Collectors in go can manage the memory but not other resources like file handles, network connections, and goroutines.

Let’s use a common example: opening files repeatedly without closing them, which is a classic resource leak scenario.

  1. Program Without Handling Resource Leaks

package main

import (
"fmt"
"os"
)

func leakFiles() {
for i := 0; i < 1000; i++ {
file, err := os.Open("nginx.conf")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
// Intentionally not closing the file (leak)
_ = file
}
}

func main() {
leakFiles()
fmt.Println("Completed without closing files")
}

2. Program with Proper Resource Cleanup

package main

import (
"fmt"
"os"
)

func handleFilesProperly() {
for i := 0; i < 1000; i++ {
file, err := os.Open("nginx.conf")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
_ = file
}
}

func main() {
handleFilesProperly()
fmt.Println("Completed with proper resource cleanup")
}

Graph below illustrates the difference in performance when a file is not closed versus when it is properly closed after use

Have you ever noticed a error while connecting to code

ERROR: too many clients already

How did I handle it ?

package main

import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)

func main() {
db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close() // Ensure the database connection is closed when main() exits

// Query the database
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close() // Ensure the rows are closed after use

for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("User: %d, %s\n", id, name)
}
}

We use the defer keyword to close the database connection and the rows after their use:

  • defer db.Close(): This ensures that, no matter what happens, the database connection will be closed when the main function finishes. If there's an error or the function returns prematurely, db.Close() will still be executed, releasing the connection back to the connection pool.
  • defer rows.Close(): Similarly, this ensures that the rows object is closed once we're done reading the query results. If we forget to close the rows object, it can lead to the same issue, where resources for the query result remain in use.

Thank you for reading ! !

--

--

No responses yet