воскресенье, 3 мая 2020 г.

Паника, следы стека и способы восстановления в Golang

Паника - исключение в Go

Паника (panic) похожа на исключения (exceptions) C++ и Java, но предназначена только для ошибок времени выполнения, таких как следование по нулевому указателю или попытка индексировать массив за пределами границ. Для обозначения таких событий, как конец файла (EOF), программы Go используют встроенный тип error.

Паника останавливает нормальное выполнение goroutine:

  • Когда программа паникует, она сразу начинает раскручивать стек вызовов.
  • Это продолжается до тех пор, пока программа не выйдет из строя и не напечатает трассировку стека,
  • или пока не будет вызвана встроенная функция recover.

Паника вызвана либо ошибкой во время выполнения, либо явным вызовом встроенной функции panic.

Следы стека

Трассировка стека - отчет обо всех активных кадрах стека - обычно выводится на консоль при возникновении паники. Следы стека могут быть очень полезны для отладки:

  • вы не только видите, где произошла ошибка,
  • но и как программа прибыла в это место.

Интерпретация трассировки стека

Вот пример трассировки стека:

goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
    /usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
    ../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
    ../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
    /usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
    /usr/local/go/src/testing/testing.go:789 +0x2de

Это можно прочитать снизу вверх:

  • testing.(*T).Run вызвал testing.tRunner,
  • который вызвал bit.TestMax,
  • который вызвал bit.(*Set).Max,
  • который вызвал panic,
  • который вызвал testing.tRunner.func1.

Строки с отступом показывают исходный файл и номер строки, по которой была вызвана функция. Шестнадцатеричные числа относятся к значениям параметров, включая значения указателей и внутренних структур данных.

Распечатать и записать трассировку стека

Чтобы напечатать трассировку стека для текущей программы, используйте debug.PrintStack из пакета runtime/debug.

Вы также можете программно проверить текущую трассировку стека, вызвав runtime.Stack.

Уровень деталей

Переменная GOTRACEBACK управляет количеством выходных данных, генерируемых при сбое программы Go.

  • GOTRACEBACK=none пропускает трассировки стека goroutine полностью.
  • GOTRACEBACK=single (по умолчанию) печатает трассировку стека для текущей go-процедуры (goroutine), исключая функции, внутренние для системы времени выполнения. Ошибка выводит трассировки стека для всех go-процедур, если текущая go-процедура не существует или ошибка является внутренней по отношению к среде выполнения.
  • GOTRACEBACK=all добавляет трассировки стека для всех пользовательских go-процедур.
  • GOTRACEBACK=system, как all, но добавляет кадры стека для функций времени выполнения (run-time) и показывает go-процедуры, созданные внутри во время выполнения.

Recover и поимка паники

Встроенная функция recover может быть использована для восстановления контроля над паникующей goroutine и возобновления нормального выполнения.

  • Вызов recover останавливает раскручивание и возвращает аргумент, переданный в panic.
  • Если goroutine не паникует, recover возвращает nil.

Поскольку единственный код, который выполняется при разматывании, находится внутри отложенных (defer) функций, recover полезна только внутри таких функций.

Пример обработчика паники

func main() {
    n := foo()
    fmt.Println("main received", n)
}

func foo() int {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    m := 1
    panic("foo: fail")
    m = 2
    return m
}

Вывод:

foo: fail
main received 0

Поскольку паника возникла до того, как foo вернула значение, n по-прежнему имеет начальное нулевое значение.

Вернуть значение

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

func main() {
    n := foo()
    fmt.Println("main received", n)
}

func foo() (m int) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            m = 2
        }
    }()
    m = 1
    panic("foo: fail")
    m = 3
    return m
}

Вывод:

foo: fail
main received 2

Тест паники (утилитная функция)

В этом примере мы используем отражение, чтобы проверить, имеют ли список переменных интерфейса типы, соответствующие параметрам данной функции. Если это так, мы вызываем функцию с этими параметрами, чтобы проверить, есть ли паника.

// Panics сообщает, паникует ли функция f с параметрами p.
func Panics(f interface{}, p ...interface{}) bool {
    fv := reflect.ValueOf(f)
    ft := reflect.TypeOf(f)
    if ft.NumIn() != len(p) {
        panic("неверное количество аргументов")
    }
    pv := make([]reflect.Value, len(p))
    for i, v := range p {
        if reflect.TypeOf(v) != ft.In(i) {
            panic("неверный тип аргумента")
        }
        pv[i] = reflect.ValueOf(v)
    }
    return call(fv, pv)
}

func call(fv reflect.Value, pv []reflect.Value) (b bool) {
    defer func() {
        if err := recover(); err != nil {
            b = true
        }
    }()
    fv.Call(pv)
    return
}


Читайте также:


Комментариев нет:

Отправить комментарий