How to handle defer calls with os.Exit in GO? [SOLVED]


GO, GOLANG Solutions

Author: Tuan Nguyen
Reviewer: Deepak Prasad

In this tutorial, we will guide you on how to handle defer calls with the os.Exit(). The defer keyword in Go is a statement used to defer the execution of a function until the surrounding function returns. In other words, the defer statement postpones the execution of a function or statement until the end of the function calling the function. The defer statement is invoked when the surrounding function executes a return statement, reaches the end of its function body, or because the surrounding goroutine is panicking.

 

The execution of the defer functions is not always guaranteed

As mentioned above, the defer function will be invoked when the main function return. However, the deferred functions are not always guaranteed to be executed. For example, invoking os.Exit or pressing CTRL + C to interrupt the program will result in your deferred functions not being called.

func Exit(code int): Exit causes the current program to exit with the given status code. Conventionally, code zero indicates success, non-zero an error. The program terminates immediately; deferred functions are not run.

Let’s take a look at the example below to see how it occurs:

package main

import (
	"os"
	"time"
)

func main() {
	defer func() {
		fmt.Println("This is a defer function")
	}()

	time.Sleep(5 * time.Second)
	os.Exit(1)
}

Output:

exit status 1

 

Method-1: Handle defer calls with the os.Exit

If you want to make sure the defer function will be called even if you use the os.Exit() function, you can re-arrange your code as the code shown below. By using a function that returns a code and has a defer function inside it, we can make sure the defer function is eventually invoked even when we can the os.Exit() function.

package main

import (
	"fmt"
	"os"
)

func deferFunc() int {
        // defer function
        defer func() {
        fmt.Println("This is the defer function!")
        }()
        
        // return a random code
        return 1
}

func main() {
	fmt.Println("This is the main function!")
	os.Exit(deferFunc())
}

Output: 

This is the main function!
This is the defer function!
exit status 1

 

Method-2: Wrap the os.Exit inside a defer function

We can wrap the os.Exit() function inside a defer function to make sure other defer calls will be invoked. The example below has 3 defer functions, one of which wraps the os.Exit() function:

package main

import (
	"fmt"
	"os"
)

func main() {
	exitCode := 0

	defer func() { os.Exit(exitCode) }()
	defer func() {
		fmt.Println("This is defer function no 1")
	}()
	defer func() {
		fmt.Println("This is defer function no 2")
	}()

	testNum := 1
	for testNum < 100 {
		fmt.Println("Test number:", testNum)
		if testNum > 3 {
			// update the exitCode
			exitCode = 1
			return
		}
		testNum += 1
	}
}

Output:

Test number: 1
Test number: 2
Test number: 3
Test number: 4
This is defer function no 2
This is defer function no 1
exit status 1

 

Method-3: Handle defer calls with the os.Exit and the Goexit()

func Goexit(): Goexit terminates the goroutine that calls it. No other goroutine is affected. Goexit runs all deferred calls before terminating the goroutine. Because Goexit is not a panic, any recover calls in those deferred functions will return nil.

Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes. Because we call Goexit() from the main goroutine, at the top of main you need to call defer os.Exit(0):

The example below how to use the Goexit() to handle defer calls with the os.Exit():

package main

import (
	"fmt"
	"os"
	"runtime"
	"time"
)

func main() {
	defer os.Exit(1)
	defer func() {
		fmt.Println("This is the defer function")
	}()

	fmt.Println("This is the main function")
	time.Sleep(3 * time.Second)
	runtime.Goexit()
}

Output:

 

Method-4: Handle defer calls with the os.Exit and panic, recover

We can use panic and recover to handle the defer calls in this case. The panic will invoke the defer functions but will also always exit with a non-0 status code and dump a stack trace. We can override the behavior of the panic as the code shown below:

package main

import (
	"fmt"
	"os"
)

type Exit struct{ Code int }

// exit handler
func exitHandler() {
        fmt.Println("This is the defer function")
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok == true {
			os.Exit(exit.Code)
		}
		panic(e) // not an Exit
	}
}

func main() {
	defer exitHandler() // the handler
	defer fmt.Println("This is the main function")
	panic(Exit{2})
}

Output:

This is the main function
This is the defer function
exit status 2

 

Summary

The first example shows us that the deferred functions are not always guaranteed to be executed (in the case of using os.Exit() function). To handle the defer calls with os.Exit, we can use a function that returns a code and has a defer function inside it, or wrap the os.Exit inside a defer function. Another way to ensure the defer function will be invoked is by using the runtime.Goexit() function. The last method to take control of the defer calls with os.Exit is by panic and recovery keywords.

 

References

https://pkg.go.dev/os#Exit
https://pkg.go.dev/runtime#Goexit
How to exit a go program honoring deferred calls?

 

Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment