2

Hello everyone, I am fried fish.

At the scene of an accident, after an emergency recovery, he was investigating the code for a while. When I look back, this error reminder is obviously a fatal error, which is better positioned.

But at this time, he was actually checking whether the panic-recover was missing. I was shocked...

Today, I will share with you the types of errors and the scenarios in which they will be triggered.

Error type

error

The first is the most standard error error in Go, which is actually an interface{}.

as follows:

type error interface {
    Error() string
}

In daily engineering, we only need to create any structure and implement the Error method, which can be considered as an error type.

as follows:

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

Call the standard library API externally, generally as follows:

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

We will agree that the last parameter is of type error, which is generally common in the second parameter, and there can be a customary habit.

panic

The second is the exception handling panic in Go, which can generate abnormal errors. Combining panic+recover can reverse the running state of the program.

as follows:

package main

import "os"

func main() {
    panic("a problem")

    _, err := os.Create("/tmp/file")
    if err != nil {
        panic(err)
    }
}

Output result:

$ go run panic.go
panic: a problem
goroutine 1 [running]:
main.main()
    /.../panic.go:12 +0x47
...
exit status 2

If recover is not used as a capture, the program will be interrupted. Because of this, people often mistakenly believe that the program interruption is 100% caused by panic.

This is a misunderstanding.

throw

The third type of error that Go beginners often step on and don't know about is the fatal error throw.

This type of error cannot be called actively on the user side. It is called by the bottom layer of Go itself, such as the common concurrent read and write of map, which is triggered by this.

The source code is as follows:

func throw(s string) {
    systemstack(func() {
        print("fatal error: ", s, "\n")
    })
    gp := getg()
    if gp.m.throwing == 0 {
        gp.m.throwing = 1
    }
    fatalthrow()
    *(*int)(nil) = 0 // not reached
}

According to the above procedure, the current instance of G will be obtained, and the throwing state of its M will be set to 1.

After the status is set, the fatalthrow method will be called to perform real crash related operations:

func fatalthrow() {
    pc := getcallerpc()
    sp := getcallersp()
    gp := getg()
    
    systemstack(func() {
        startpanic_m()
        if dopanic_m(gp, pc, sp) {
            crash()
        }

        exit(2)
    })

    *(*int)(nil) = 0 // not reached
}

The main logic is to send the _SIGABRT semaphore, and finally call the exit method to exit, so you will find that this is a "fatal" error that cannot be stopped or stopped.

Fatal scene

For this reason, as a "mature" Go engineer, in addition to ensuring the robustness of my own program, I also collected some fatal error scenarios on the Internet and shared them with you.

Learn and avoid these deadly scenarios together, try to get an A at the end of the year, and don't carry a P0 accident on your back.

Concurrent read and write map

func foo() {
    m := map[string]int{}
    go func() {
        for {
            m["煎鱼1"] = 1
        }
    }()
    for {
        _ = m["煎鱼2"]
    }
}

Output result:

fatal error: concurrent map read and map write

goroutine 1 [running]:
runtime.throw(0x1078103, 0x21)
...

Stack memory exhausted

func foo() {
    var f func(a [1000]int64)
    f = func(a [1000]int64) {
        f(a)
    }
    f([1000]int64{})
}

Output result:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e1bf0 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow

runtime stack:
runtime.throw(0x1074ba3, 0xe)
        /usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:1117 +0x72
runtime.newstack()
...

Start the nil function as a goroutine

func foo() {
    var f func()
    go f()
}

Output result:

fatal error: go of nil func value

goroutine 1 [running]:
main.foo()
...

goroutines deadlock

func foo() {
    select {}
}

Output result:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:
main.foo()
...

Thread limit exhausted

If your goroutines are blocked by IO operations, new threads may be started to execute your other goroutines.

There is a default limit on the maximum number of threads in Go. If this limit is reached, your application will crash.

The following output will appear:

fatal error: thread exhaustion
...

You can increase the number of threads by calling the runtime.SetMaxThreads method, but you also need to consider whether there is a problem with the program.

Exceeded available memory

If you perform operations such as downloading large files, etc. As a result, the application program occupies too much memory and the program increases, leading to OOM.

The following output will appear:

fatal error: runtime: out of memory
...

It is recommended to dispose of some programs or change to a new computer.

Summarize

In today's article, we introduced the three types of errors in the Go language. Among them, the fatal error, which is the least common to everyone, but is easy to roll over, is introduced, and some classic cases are given.

I hope you can circumvent it in the Have you ever encountered the scene ?

If you have any questions, welcome feedback and exchanges in the comment area. The best relationship between . Your likes is the biggest motivation for the creation of fried fish

The article is continuously updated, and you can read it on Search [My brain is fried fish], this article 161cd3cdb17538 GitHub github.com/eddycjy/blog has been included, learning Go language can see Go learning map and route cd3cdb1753f Go.

refer to

  • Are all runtime errors recoverable in Go?

煎鱼
8.4k 声望12.8k 粉丝