[Golang] Ошибки, которые нельзя обработать

от автора

Бывает что мы хотим добавить новую функциональность в новый сервис и всегда хочется это сделать быстро. И иногда приходит мысль написать рабочий вариант, а после этого исправлять баги. Может показаться, что если мы разрабатываем новую функциональность, мы не можем затронуть существующий функционал — у нас же http или grpc фреймворк ловит все паники и обрабатывает их. Но это не всегда так и в этой статье я хочу рассказать о некоторых ошибках, за которыми нужно всегда внимательно смотреть, потому что они могут привести к падению сервиса


Паника в горутине

Если мы в обработчике будем использовать горутину, которая по каким-то причинам вызвала панику, Golang не сможет обработать эту панику и приложение упадет с ошибкой и все запросы, которые сейчас обрабатывает сервис оборвутся.

Eсли написать примерно вот такой код

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 }() }

То при вызове функции CreateUser сервис упадет.

Для того, что бы исправить это, нужно обрабатывать паники в каждой горутине

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 }() }

и приложение не будет падать и все будут счастливы.

Бесконечная рекурсия

Если мы по какой-то причине используем рекурсию, то нужно обязательно смотреть что при любых входных параметрах не будет бесконечной рекурсии, так как обработать переполнение стека в Golang нельзя (бесконечная рекурсия приводит к переполнения стека) и приложение падает.

Допустим мы написали такую реализация вычисления числа Фибоначчи:

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

И если у нас в коде будет вызов функции Fib с отрицательным числом, то приложение упадет с ошибкой fatal error: stack overflow.

Это может удивить людей, которые писали на скриптовых языках таких как Ruby или Python и начинают писать на Golang. Так как в скриптовых языках можно довольно просто поймать исключение, которое показывает что стек достиг максимального значения:

def fib(n):     if n == 1:         return 1     return fib(n- 1) + fib(n - 2)  try:     fib(0) except RecursionError as e:     print(e)

Код на Python выше просто выведет в консоль maximum recursion depth exceeded in comparison и приложение продолжит работу.

Работа с unsafe

При работе с модулем unsafe довольно просто сделать так что бы приложение упало. Поэтому не стоит использовать этот модуль, если все тщательно не проверено и не протестировано.

Допустим мы решили не копировать строку, а преобразовать ее в слайс байтов и написали вот такую функцию:

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

Теперь мы можем изменять строку:

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

Но если случайно передать строку, которая известна на этапе компиляции и попробовать ее изменить, то будет ошибка, которая приведет к остановке сервера

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

Заключение

В данной статье я разобрал некоторые ошибки, которыми можно довольно просто положить сервер. Таких ошибок в Golang еще много, поэтому нужно стараться покрывать свой код тестами, которые проверяют работоспособность кода, благо в Golang есть довольно хорошие инструменты для написания тестов.


ссылка на оригинал статьи https://habr.com/ru/post/689356/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *