17

I think an equivalent question is - do all runtime errors, which may be fatal, panic? Because anything that panics should be recoverable. I'm not talking about recovering from things like os.Exit(), or log.Fatal(), or bugs in the Go runtime, or someone tripping over the power cord, but from other runtime errors which will lead to the program crashing.

Here's an example of a runtime error that can be caught via panic/recover:

package main

import (
    "fmt"
)

func errorHandler() {
    r := recover()
    err := r.(error)
    if err == nil {
        return
    }
    fmt.Println(err.Error())
}

func foo() {
    defer errorHandler()
    smallSlice := []int{1, 0, 1}
    smallSlice[10] = 1
}

func main() {
    foo()
    fmt.Println("recovery, end of main")
}

output:

runtime error: index out of range
recovery, end of main

Are there any examples where runtime errors will just crash the program without a recoverable panic?

chrishiestand
  • 2,800
  • 1
  • 25
  • 25
  • 2
    Take a look at all the `throw()` calls in the runtime if you want to see most of the fatal runtime errors. – JimB Aug 14 '19 at 13:57

2 Answers2

17

First change your errorHandler() because if there are no panics, r will be nil and thus the type assertion would fail:

func errorHandler() {
    if r := recover(); r != nil {
        fmt.Println(r)
    }
}

And now here are some examples with code to produce unrecoverable runtime errors:

1. Out of memory:

func foo() {
    defer errorHandler()
    _ = make([]int64, 1<<40) // You have to change the size on 32-bit systems
}

2. Concurrent map writes and reads

For details see How to recover from concurrent map writes?, for example see Can marshalling a map[string]string to json return an error?

func foo() {
    defer errorHandler()
    m := map[string]int{}

    go func() {
        for {
            m["x"] = 1
        }
    }()
    for {
        _ = m["x"]
    }
}

3. Stack memory exhaustion

For details, see Does Go have an "infinite call stack" equivalent?

func foo() {
    defer errorHandler()

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

4. Attempting to launch a nil function as a goroutine

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

5. All goroutines are asleep - deadlock

Title says it all. Here's a simple code to block the current goroutine, but if you have launched other goroutines, you obviously won't experience the crash. See other examples here: Go project's main goroutine sleep forever?

func foo() {
    defer errorHandler()
    select {}
}

6. Thread limit exhaustion

If your goroutines get blocked by IO operations, new threads may be started to execute your other goroutines. There is obviously a limit to the max thread count, if it is reached, your app will crash.

cn007b
  • 16,596
  • 7
  • 59
  • 74
icza
  • 389,944
  • 63
  • 907
  • 827
  • 4
    We could on an on here, there's a lot of `throw()`s in the runtime package, and a data race could really get you anything. Regular SIGSEGVs (without `debug.SetPanicOnFault`), `sync: unlock of unlocked mutex`, `inconsistent mutex state` , `alloc too large`, all `throw()` off the top of my head. – JimB Aug 14 '19 at 13:55
9

Are there any examples where runtime errors will just crash the program without a recoverable panic?


For example, Go Out-Of-Memory (OOM) errors are not recoverable.


src/runtime/panic.go:

// fatalpanic implements an unrecoverable panic. It is like fatalthrow, except
// that if msgs != nil, fatalpanic also prints panic messages and decrements
// runningPanicDefers once main is blocked from exiting.
func fatalpanic(msgs *_panic) {
    // ...
}

// fatalthrow implements an unrecoverable runtime throw. It freezes the
// system, prints stack traces starting from its caller, and terminates the
// process.
func fatalthrow() {
    // ...
}
peterSO
  • 158,998
  • 31
  • 281
  • 276