go defer,panic,recover explain go exception handling in detail

Keywords: Go

Reprint   https://www.jianshu.com/p/63e3d57f285f

In golang, defer, panic and recover are three common features. When used together, they can act as try... catch... In other languages, and defer itself is like a destructor in other languages

defer

A function will be followed by defer, but the function will not be executed immediately. Instead, the defer function will not be executed until the program containing it returns (the function containing it executes the return statement, automatically returns at the end of the function, and the corresponding goroutine panic). It is usually used for resource release, log printing, exception capture, etc

func main() {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    /**
     * Here, defer should be written after err judgment instead of os.Open
     * If the resource does not succeed, there is no need to release the resource
     * If err does not release resources for nil, panic may occur
     */
    defer f.Close()
}

If there are multiple defer functions, the call sequence is similar to the stack. The later the defer function is, the earlier it is executed (LIFO)

func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    defer fmt.Println(4)
}

result:

4
3
2
1

If the outer function containing the defer function has a return value, and the return value may be modified in the defer function, the actual return value of the outer function may be inconsistent with what you think. It's easy to step on the pit here. Here are a few examples 🌰

Example 1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

Example 2

func f() (r int) {
    t := 5
    defer func() {
        t = t + 5
    }()
    return t
}

Example 3

func f() (r int) {
    defer func(r int) {
        r = r + 5
    }(r)
    return 1
}

Please don't look down, run the results of the above three examples in your heart, and then verify them
You may think that the result of example 1 is 0, the result of example 2 is 10, and the result of example 3 is 6. I'm sorry to tell you that these three results are wrong
Why? The most important thing is to understand that the return xxx statement is not an atomic instruction
The outer function that contains the defer function returns the procedure: first assign the value to the return value, then call the defer function, and finally return to the higher level calling function, and use a simple conversion rule to rewrite return xxx.

Return value = xxx
 call defer function(There may be operations to modify the return value)
return Return value

Example 1 can be rewritten like this

func f() (result int) {
    result = 0
    //Execute the defer function before return
    func() {
        result++
    }()
    return
}

So the return value of example 1 is 1

Example 2 can be rewritten like this

func f() (r int) {
    t := 5
    //assignment
    r = t
    //Before return, execute the defer function. The defer function does not modify the return value r, but modifies the variable t
    func() {
        t = t + 5
    }
    return
}

So the result of example 2 is 5

Example 3 can be rewritten like this

func f() (r int) {
    //Assign a value to the return value
    r = 1
    /**
     * The modified r is the value of the function parameter, which is passed in from the outside
     * func(r int){}The scope of the inner r is only within this func. Modifying this value will not change the r value outside func
     */
    func(r int) {
        r = r + 5
    }(r)
    return
}

So the result of example 3 is 1

The parameter value of the defer function is determined when the defer is declared

When the defer function is declared, there are two ways to reference external variables: as function parameters and as closures
As a function parameter, the value is passed to the defer when the defer is declared, and the value is cached. When the defer is called, the cached value is used for calculation (as in example 3 above)
As a closure reference, the current value is determined according to the whole context when the defer function is executed
Look 🌰

func main() {
    i := 0
    defer fmt.Println("a:", i)
    //The closure call transfers the external i to the closure for calculation without changing the value of i, such as example 3 above
    defer func(i int) {
        fmt.Println("b:", i)
    }(i)
    //Closure call to capture i under the same scope for calculation
    defer func() {
        fmt.Println("c:", i)
    }()
    i++
}

result:

c: 1
b: 0
a: 0

by smoke_zl

panic and recover

func panic(v interface{})

Chinese and English description
The panic built-in function stops normal execution of the current goroutine.When a function F calls panic, normal execution of F stops immediately.Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic,terminating G's execution and running any deferred functions.This continues until all functions in the executing goroutine have stopped,in reverse order. At that point, the program is terminated and the error condition is reported,including the value of the argument to panic. This termination sequence is called panicking and can be controlled by the built-in function recover.
The panic built-in function stops the normal execution of the current goroutine. When function f calls panic, the normal execution of function f is immediately stopped, and then run all the defer functions in F function, and then f returns to the calling function. For caller g, the behavior of F function is like panic, terminating the execution of G and running the defer function in G, This process continues until all goroutine functions are executed. Panic can be captured through the built-in recover.

func recover() interface{}

Chinese and English description
The recover built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function (but not any function called by it) stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the goroutine is not panicking, or if the argument supplied to panic was nil, recover returns nil. Thus the return value from recover reports whether the goroutine is panicking.
The recover built-in function is used to manage goroutine with panic behavior. Recover runs in the defer function to obtain the error value thrown by panic and restore the program to the normal execution state. If recover is called outside the defer function, recover will not stop and catch panic errors. If there is no panic in goroutine or the value of the captured panic is nil, the return value of recover is also nil. Thus, the return value of recover indicates whether the current goroutine has panic behavior

Here are several issues that need attention through 🌰 performance

1. If the function of the defer expression is defined after the panic, the function cannot be executed after the panic

panic before defer

func main() {
    panic("a")
    defer func() {
        fmt.Println("b")
    }()
}

As a result, b is not printed:

panic: a

goroutine 1 [running]:
main.main()
    /xxxxx/src/xxx.go:50 +0x39
exit status 2

After defer, panic

func main() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

As a result, b is printed normally:

b
panic: a

goroutine 1 [running]:
main.main()
    /xxxxx/src/xxx.go:50 +0x39
exit status 2

2. When a panic occurs in F, the F function will terminate immediately. It will not execute the contents after the panic in F function, but will not return immediately. Instead, it will call F's defer. If there is a recover capture in F's defer, f will return normally after executing the defer, and function G calling function f will continue to execute normally

func G() {
    defer func() {
        fmt.Println("c")
    }()
    F()
    fmt.Println("Continue execution")
}

func F() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Catch exception:", err)
        }
        fmt.Println("b")
    }()
    panic("a")
}

result:

Catch exception: a
b
 Continue execution
c

3. If there is no recover capture in F's defer, throw the panic into G, and the G function will terminate immediately. It will not execute the subsequent contents in the G function, but will not return immediately. Instead, call G's defer... And so on

func G() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Catch exception:", err)
        }
        fmt.Println("c")
    }()
    F()
    fmt.Println("Continue execution")
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

result:

b
 Catch exception: a
c

4. If there is no recover y, the program will terminate abnormally when the thrown panic reaches the highest level function of the current goroutine

func G() {
    defer func() {
        fmt.Println("c")
    }()
    F()
    fmt.Println("Continue execution")
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    panic("a")
}

result:

b
c
panic: a

goroutine 1 [running]:
main.F()
    /xxxxx/src/xxx.go:61 +0x55
main.G()
    /xxxxx/src/xxx.go:53 +0x42
exit status 2

5. Recovers are captured in the current goroutine. That is to say, for the outer function of creating goroutine, if a panic occurs inside goroutine and recover is not used inside goroutine, the outer function cannot be captured with recover, which will cause program crash

func G() {
    defer func() {
        //recover outside goroutine
        if err := recover(); err != nil {
            fmt.Println("Catch exception:", err)
        }
        fmt.Println("c")
    }()
    //Create goroutine and call F function
    go F()
    time.Sleep(time.Second)
}

func F() {
    defer func() {
        fmt.Println("b")
    }()
    //goroutine throws panic internally
    panic("a")
}

result:

b
panic: a

goroutine 5 [running]:
main.F()
    /xxxxx/src/xxx.go:67 +0x55
created by main.main
    /xxxxx/src/xxx.go:58 +0x51
exit status 2

6. recover returns the interface {} type instead of the error type in go. If the outer function needs to call err.Error(), it will compile errors or panic during execution

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Catch exception:", err.Error())
        }
    }()
    panic("a")
}

Compilation error, result:

err.Error undefined (type interface {} is interface with no methods)

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Catch exception:", fmt.Errorf("%v", err).Error())
        }
    }()
    panic("a")
}

result:

Catch exception: a

reference resources:
https: //golang.org/pkg/builtin/#recover
https: //www.w3cschool.cn/go_internals/go_internals-drq7282o.html


 

Posted by cookiemonster4470 on Tue, 07 Sep 2021 14:51:10 -0700