[Golang] Errors that cannot be handled

It happens that we want to add new functionality to a new service and we always want to do it quickly. And sometimes the idea comes to write a working version, and then fix bugs. It may seem that if we develop new functionality, we cannot affect the existing functionality – our http or grpc framework catches all panics and processes them. But this is not always the case, and in this article I want to talk about some errors that you should always watch carefully, because they can lead to a service crash.


Panic in Goroutine

If we use a goroutine in the handler, which for some reason caused a panic, Golang will not be able to process this panic and the application will crash with an error and all requests that the service is currently processing will be terminated.

If you write something like this code

type User struct {
	Email string
}

func UpsertUser(r *http.Request) (User, error) {
	return User{}, nil
}

func SendEmail(u User) {
	panic("sending email is not implemented")
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
	user, err := UpsertUser(r)

	if err != nil {
		// handling error
	}

	go func() {
		SendEmail(user)
		// may be something else
	}()
}

Then when the CreateUser function is called, the service will fall.

In order to fix this, you need handle panics in each goroutine

func CreateUser(w http.ResponseWriter, r *http.Request) {
	user, err := UpsertUser(r)

	if err != nil {
		// handling error
	}

	go func() {

		defer func() {
			if err := recover(); err != nil {
				log.Printf("panic recovered: %v", err)
			}
		}()
		SendEmail(user)
		// may be something else
	}()
}

and the app won’t crash and everyone will be happy.

Infinite recursion

If for some reason we use recursion, then we need to make sure that for any input parameters there will not be an infinite recursion, since it is impossible to handle a stack overflow in Golang (infinite recursion leads to a stack overflow) and the application crashes.

Suppose we wrote the following implementation of calculating the Fibonacci number:

func Fib(n int64) int64 {
	if n == 1 {
		return 1
	}
	return Fib(n-1) + Fib(n-2)
}

And if we have a function call in our code Fib with a negative number, the application will crash with an error fatal error: stack overflow.

This may surprise people who have written in scripting languages ​​such as Ruby or Python and are starting to write in Golang. Since in scripting languages ​​it is quite easy to catch an exception that indicates that the stack has reached its maximum value:

def fib(n):
    if n == 1:
        return 1
    return fib(n- 1) + fib(n - 2)

try:
    fib(0)
except RecursionError as e:
    print(e)

The Python code above will simply print to the console maximum recursion depth exceeded in comparison and the application will continue to run.

Working with unsafe

When working with the module unsafe It’s pretty easy to make the app crash. Therefore, you should not use this module unless everything has been carefully checked and tested.

Suppose we decide not to copy the string, but to convert it into a slice of bytes and write the following function:

func ToSlice(a string) []byte {
	return *(*[]byte)(unsafe.Pointer(&a))
}

Now we can change the line:

	a := string([]byte("Andrey Berenda"))
	b := ToSlice(a)
	b[5] = 'i'
	fmt.Println(a) // Andrei Berenda

But if you accidentally pass a string that is known at the compilation stage and try to change it, then there will be an error that will lead to a server stop

	a := "Andrey Berenda"
	b := ToSlice(a)
	b[5] = 'i'
	fmt.Println(a) // fatal error: fault

Conclusion

In this article, I have analyzed some of the errors that can be quite easy to put the server. There are still many such errors in Golang, so you should try to cover your code with tests that check the functionality of the code, since Golang has pretty good tools for writing tests.

Similar Posts

Leave a Reply Cancel reply