Показаны сообщения с ярлыком встроенная функция recover в Go. Показать все сообщения
Показаны сообщения с ярлыком встроенная функция recover в Go. Показать все сообщения

четверг, 18 ноября 2021 г.

Go для Java разработчиков: ошибки, паника и восстановление

Ошибки

Там, где Java обычно использует исключения, Go имеет два разных механизма:

  • большинство функций возвращают ошибки;
  • только действительно невосстановимые условия, такие как индекс вне допустимого диапазона, создают исключения во время выполнения.

Многозначный возврат Go позволяет легко вернуть подробное сообщение об ошибке вместе с обычным возвращаемым значением. По соглашению такие сообщения имеют тип error, простой встроенный интерфейс.

type error interface {
    Error() string
}

Например, функция os.Open возвращает ненулевое значение ошибки, когда не удается открыть файл.

func Open(name string) (file *File, err error)

В следующем коде для открытия файла используется os.Open. Если возникает ошибка, он вызывает log.Fatal, чтобы распечатать сообщение об ошибке и остановиться.

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// что-то делаем с открытым *File f

Интерфейс error требует только метода Error, но конкретные реализации ошибок часто имеют дополнительные методы, позволяющие вызывающим абонентам проверять подробности ошибки.

Паника и восстановление

Паника (panic) - это ошибка времени выполнения, которая раскручивает стек горутины, по пути запускает любые отложенные функции, а затем останавливает программу.

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

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

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

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


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


воскресенье, 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
}


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


четверг, 30 апреля 2020 г.

Отложить вызов функции (с возвращаемым значением) в Golang

Оператор defer откладывает выполнение функции до тех пор, пока не вернется окружающая функция, либо в обычном режиме, либо через panic.

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

Вывод:

Hello
World

Отложенные вызовы выполняются даже при панике функции:

func main() {
    defer fmt.Println("World")
    panic("Stop")
    fmt.Println("Hello")
}

Вывод:

World
panic: Stop

goroutine 1 [running]:
main.main()
    ../main.go:3 +0xa0

Порядок исполнения

Аргументы отложенного вызова вычисляются немедленно, даже если вызов функции не выполняется до тех пор, пока не вернется окружающая функция.

Если имеется несколько отложенных вызовов функций, они выполняются в порядке "последний пришел - первым вышел".

func main() {
    fmt.Println("Hello")
    for i := 1; i <= 3; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("World")
}

Вывод:

Hello
World
3
2
1

Используйте func для возврата значения

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

В этом примере функция foo возвращает "Change World".

func foo() (result string) {
    defer func() {
        // изменяет значение в самый последний момент
        result = "Change World" 
    }()
    return "Hello World"
}

Применение defer

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

Закрытие файла

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

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Обработка ошибок: поймать panic

Defer также может быть использован для восстановления (recover) после panic и обновления возвращаемого значения.


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


четверг, 13 февраля 2020 г.

Defer, Panic, и Recover в Golang

Go имеет обычные механизмы управления потоком: if, for, switch, goto. Он также содержит утверждение (statement) go для запуска кода в отдельной goroutine. Но в этом посте мы обсудим некоторые из менее распространенных утверждений для управления потоком исполнения программы в Go: defer, panic, и recover.

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

Например, давайте посмотрим на функцию, которая открывает два файла и копирует содержимое одного файла в другой:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

Это работает, но есть ошибка. Если вызов os.Create завершится неудачно, функция вернется без закрытия исходного файла. Это можно легко исправить, поместив вызов src.Close перед вторым return утверждением, но если бы функция была более сложной, проблему было бы не так легко заметить и решить. Вводя defer утверждения, мы можем гарантировать, что файлы всегда закрыты:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

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

Поведение defer утверждений является простым и предсказуемым. Есть три простых правила:

1. Аргументы отложенной функции оцениваются, когда оценивается оператор defer.

В этом примере выражение "i" вычисляется при отсрочке вызова Println. Отложенный вызов выведет "0" после возврата из функции.

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

2. Отложенные вызовы функций выполняются в порядке "последний пришел - первый вышел" (Last In First Out, LIFO) после возврата окружающей функции.

Эта функция печатает "3210":

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

3. Отложенные функции могут читать и присваивать возвращаемой функции именованные возвращаемые значения.

В этом примере отложенная функция увеличивает возвращаемое значение i после возврата окружающей функции. Таким образом, эта функция возвращает 2:

func c() (i int) {
    defer func() { i++ }()
    return 1
}

Это удобно для изменения значения ошибки, возвращаемого функцией; мы увидим пример этого в ближайшее время.

Panic - это встроенная функция, которая останавливает обычный поток контроля и начинает паниковать. Когда функция F вызывает panic, выполнение F останавливается, все отложенные (defer) функции в F выполняются нормально, а затем F возвращается к своему вызывающему. Для вызывающей стороны F ведет себя как вызов panic. Процесс продолжается вверх по стеку до тех пор, пока не будут возвращены все функции в текущей процедуре, после чего программа завершится сбоем. Panic можно инициировать, вызывая panic напрямую. Они также могут быть вызваны ошибками во время выполнения, такими как доступ за пределы массива.

Recover - это встроенная функция, которая восстанавливает контроль над паникующими goroutine. Recover полезна только внутри отложенных (defer) функций. Во время обычного выполнения, вызов recover вернет nil и не будет иметь никакого другого эффекта. Если текущая goroutine вызывает panic, вызов recover захватит значение, переданное panic, и возобновит нормальное выполнение.

Вот пример программы, которая демонстрирует механику panic и recover:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

Функция g принимает int i и паникует, если i больше 3, иначе она вызывает себя с аргументом i + 1. Функция f откладывает функцию, которая вызывает recover и печатает восстановленное значение (если оно не равно нулю).

Программа выведет:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

Если мы удалим отложенную (defer) функцию из f, panic не восстановится и достигнет вершины стека вызовов программы, завершив программу. Эта измененная программа выведет:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
 
panic PC=0x2a9cd8
[stack trace omitted]

Для реального примера panic и recover можно посмотреть пакет json из стандартной библиотеки Go. Он кодирует интерфейс с набором рекурсивных функций. Если при обходе значения возникает ошибка, вызывается паника, чтобы развернуть стек для вызова функции верхнего уровня, который восстанавливается после паники и возвращает соответствующее значение ошибки.

Соглашение в библиотеках Go заключается в том, что даже когда пакет использует внутреннюю panic, его внешний API все еще представляет явные возвращаемые значения ошибок.

Другие способы использования defer (кроме приведенный выше примера file.Close) включают освобождение мьютекса:

mu.Lock()
defer mu.Unlock()

печать нижнего колонтитула:

printHeader()
defer printFooter()

и другие.

Таким образом, оператор defer (с panic и recover или без них) обеспечивает необычный и мощный механизм управления потоком исполнения программы. Он может использоваться для моделирования ряда функций, реализованных структурами специального назначения в других языках программирования.


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


суббота, 22 июня 2019 г.

Спецификация Go: обработка паники

Две встроенные функции, panic и recover, помогают в составлении отчетов и обработке паники во время выполнения (run-time panics) и программно определенных ошибочных условий.

func panic(interface{})
func recover() interface{}

При выполнении функции F явный вызов panic или паники во время выполнения (run-time panic) завершает выполнение F. Любые функции, отложенные (deferred) с помощью F, затем выполняются как обычно. Затем выполняются все отложенные функции, выполняемые вызывающей стороной F, и так далее, вплоть до любой отложенной функции верхнего уровня в выполняющейся процедуре. В этот момент программа завершается и сообщается об ошибке, включая значение аргумента для паники. Эта последовательность завершения называется паникой.

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

Функция recover позволяет программе управлять поведением паникующей программы. Предположим, что функция G откладывает функцию D, которая вызывает recover, и паника возникает в функции той же программы, в которой выполняется G. Когда выполнение отложенных функций достигает D, возвращаемым значением вызова D для recover будет значение, переданное вызову паники. Если D возвращается нормально, без начала новой паники, последовательность паники останавливается. В этом случае состояние функций, вызываемых между G и вызовом паники, отбрасывается, и нормальное выполнение возобновляется. Любые функции, отложенные G перед D, затем запускаются, и выполнение G прекращается, возвращаясь к своему вызывающему.

Возвращаемое значение recover равно nil, если выполняется любое из следующих условий:

  • аргумент паники был nil;
  • goroutine не паникует;
  • recover не была вызвана напрямую отложенной функцией.

Функция protect в приведенном ниже примере вызывает аргумент функции g и защищает вызывающих от паники во время выполнения, вызванной g.

func protect(g func()) {
    defer func() {
        // Println выполняется нормально, 
        // даже если есть паника
        log.Println("done")  
        if x := recover(); x != nil {
            log.Printf("run time panic: %v", x)
        }
    }()
    log.Println("start")
    g()
}


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