Panics in libraries that spawn goroutines - go

What if an imported library spawns a goroutine that can panic? In this case, there is nothing a developer can do to stop the program from exiting.
Like in this code, calling a bad library with deferred recover does not help as the library is spawning a goroutine that panics, and it cannot be caught by the main's recover.
Do I understand it correct that the only remedy is to be very careful when choosing dependencies for one's project and hope that the authors do not do something similar?
package main
import (
"fmt"
"time"
)
func main() {
defer func() {
r := recover()
if r != nil {
fmt.Println("panic caught")
}
}()
badLibrary()
}
func badLibrary() {
go recklessFunction()
time.Sleep(time.Second)
}
func recklessFunction() {
panic("something went wrong")
}

You're right, you can't do anything about that. Neither can you if a dependency calls os.Exit() for example.
Launching goroutines as a library is often bad practice anyway, let the client (user of the library) choose if concurrent execution (a goroutine) is wanted, adding the go keyword is trivial. There are exceptions of course.

Related

Deadlock in goroutine with GopherJS

Why is there a deadlock in the following code? I am trying to return something from the goroutine to outside
package main
import (
"fmt"
"syscall/js"
"time"
)
func test(this js.Value, i []js.Value) interface{} {
done := make(chan string, 1)
go func() {
doRequest := func(this js.Value, i []js.Value) interface{} {
time.Sleep(time.Second)
return 0
}
js.Global().Set("doRequest", js.FuncOf(doRequest))
args := []js.Value{js.ValueOf("url")}
var x js.Value
doRequest(x, args)
done <- "true"
}()
aa := <-done
fmt.Println(aa)
return 0
}
func main() {
c := make(chan bool)
js.Global().Set("test", js.FuncOf(test))
<-c
}
When I run this on a browser and call test(), the following error will be shown
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
.....
Pretty much what it says in the error message. All goroutines are asleep. main doesn't start anything and just does a channel receive, so it's blocked, and no other goroutines are running, so there's no possibility that main could ever wake up again, so the runtime panics.
If I recall correctly, unlike regular Go, GopherJS doesn't shut everything down and exit if main exits (in part because: what exactly would that even mean? The closest analog to a Go program would be to close the webpage! Which would kind of suck. So GopherJS doesn't do that.). So what you're doing to keep main alive is not necessary, strictly speaking, in GopherJS.
That said, if you say (for example) time.Sleep(time.Hour) at the end instead, then while all goroutines are still asleep (strictly speaking), main will eventually wake up, which the runtime knows, so it doesn't panic in that case.
As to your actual test function, once you do try it, you get a related error message: Uncaught Error: runtime error: cannot block in JavaScript callback, fix by wrapping code in goroutine. test does a blocking call on a channel, and GopherJS won't allow that in a function called directly from Javascript, so it panics. (When I run it in the playground, I also get Uncaught TypeError: r is not a function, but that's just fallout from the earlier error.)
I think what you're trying to do is wait for doRequest to finish, print the value, and return, but that won't work. You'll need to use a native Javascript promise, or some other asynchronous mechanism, for that.
func main() {
c := make(chan bool)
js.Global().Set("test", js.FuncOf(test))
<-c
}
You have made a channel c, and then wait to receive a value from it. Notice how c is a variable local to the main function. The reference to c is never passed anywhere else in the program, so there can never be a value sent on the c channel, and thus your main goroutine will wait forever to receive.

Recovering panic with wrapper in Go

I have came across the following code that manages recover from panic using a wrapper. I understand that panic don't propagate across goroutines and they have to be managed independently, so it needs to be handled in the goroutine where the panic occurred.
package main
import (
"fmt"
"time"
)
func main() {
go Wrap(test)()
time.Sleep(time.Second)
fmt.Println("HELLO")
}
func test() {
panic("PANIC")
}
func Wrap(f func()) func() {
return func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("RECOVERED - %v\r\n", r)
}
}()
f()
}
}
func WrapWithSignal(f func(chan bool), signal chan bool) func() {
return func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("RECOVERED - %v\r\n", r)
signal <- false
}
}()
f(signal)
}
}
What will be the use of WrapWithSignal function, and how can I use it?
Is there another way to wrap functions in order to recover that is less verbose?
When implementing long-running processes, it's possible that certain code paths will result in a panic. This is usually common for things such as uninitialized maps and pointers, as well as division by zero problems in the case of poorly validated user input.
Having a program crash completely in these cases is frequently much worse than the panic itself, and so it can be helpful to catch and handle panics.
In most web applications, it's common to catch panics and emit an http.InternalServerError message when a panic occurs.
Within the recover, you can, essentially, do anything you want, although emitting a log is common.
1) Wrap with Signal can be used to recover from panic and send a signal to the given bool channel.
The warp with signal function implements the wrap pattern and can be used to wrap a signal channel and emit an event to the channel even when a panic occurs. In this case, if the recover was not called, you would get a deadlock.
package main
import (
"errors"
"fmt"
"math/rand"
"time"
)
func main() {
signal:= make(chan bool)
go WrapWithSignal(play, signal)()
if <-signal{
fmt.Println("Congratulations, you win!")
return
}
fmt.Println("You died.")
}
func play(signal chan bool){
fmt.Println("Playing russian roulette")
rand.Seed(time.Now().UnixNano())
if rand.Intn(2) == 1{
panic(errors.New("got a bullet"))
}
signal <- true
}
2) Is there another way to wrap functions in order to recover that is less verbose?
Short answer: NO, unless you use a package. A package theoretically is always less verbose.
Long answer: Depending the situation, you can for example, create a go routine wrapper and create your own panic group based on a waiting group. It can be useful while validating user input or doing concurrent operations and collect all the errors at the end. Split it into a package and you will get verbosity.
Just a sample: https://play.golang.org/p/on9AwZkvyIZ

Infite for-loop in an `init()` function of a package – good or bad idea?

I was wondering if it's a bad idea to have an infinite for-loop in an init() function of a package or if one should avoid doing that.
Does anyone have any knowledge or experience, if this can be done or should be avoided?
Where would you use this?
This could be used for example for a package that provides some information from external sources that has to be refreshed periodically (like e.g. once per day).
I have used similar code as below, but without the "watchdog" function. Meaning the init() just started a go-routine that would run in the background and run the update process whenever a tick arrived.
Unfortunately, this update-mechanism stopped working after aprox. 3 months for unknown reasons, but the service was working fine, just with "old" data.
Simple example implementation
Full example on https://play.golang.org/p/k-GI1t9J4oP
package info
import (
"log"
"sync"
"time"
)
var (
data map[string]interface{}
lock sync.RWMutex
)
func init() {
// ticker channel
ticker := time.NewTicker(1 * time.Second).C
// "watchdog" loop
for {
log.Println("Starting Update Loop")
var wg sync.WaitGroup
wg.Add(1)
// Start asyc update process.
go func() {
defer wg.Done() //notify wg when if process ends for whatever reason
// Loop forever
// Run when a tick is received from the `ticker` channel
for {
select {
case <-ticker:
log.Println("Update ticker received")
err := update()
if err != nil {
log.Printf("ERROR: %v\n", err.Error())
}
}
}
}()
wg.Wait()
}
}
// internal update function that retrieves some information from some external system
func update() error {
lock.Lock()
defer lock.Unlock()
log.Println("Update `data`")
// retrieve information and update `data`
return nil
}
// Public function to query data
func GetInformation(key string) interface{} {
lock.RLock()
defer lock.RUnlock()
return data[key]
}
The code works and runs fine for unit-tests and also running normally.
I was wondering about long-time stability (uptimes of 1 year and more) and the such.
It's in a goroutine so it's technically fine but having behavior implemented directly in an init makes it very hard to work with for two reasons:
It's hard to test, in the same way that main is hard to test. It's much easier to test if init calls another function, which can then be tested.
It's hard to reason about. The more stuff that happens "automagically", the less sense it will make to any developer (including your future self) using that package. "OK I imported this package and used one tiny function and now somehow I went from 1% CPU use to 50% what did I do wrong" kind of things will come up and require a lot more spelunking to figure out than they should.
TL;DR there's no "long-term stability" problem but there's very likely a long-term maintainability problem.

How to set a breakpoint just before exit in golang

I am a complete beginner in golang, in fact I am debugging someone else's program to find out the cause of an unexpected exit.
I want to know how can I set a breakpoint in gdb at the "exit" routine called just before the program shuts down?
I have so far tried
gdb <program name>
run
<...wait for program to quit>
break 'runtime.goexit'
run
<...wait for program to break>
But it does not break, instead it just exits.
If you have access to source code you can add a defer to handle exit from arbitrary point like:
https://play.golang.org/p/uliAc3j7f-
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("Place breakpoint here")
if recovered := recover(); recovered != nil {
fmt.Println("Handled panic:", recovered)
}
}()
fmt.Println("Hello, playground")
panic("Something went wrong")
}
Also Go is case sensitive so try break 'runtime.Goexit' - see https://golang.org/pkg/runtime/#Goexit
But it does not break, instead it just exits.
You can use catch syscall exit (or catch sycall exit_group if you are
on Linux).
That is guaranteed to stop the program if it really exists (as opposed to being terminated by a signal).

Stack trace of go routine from imported package?

How to obtain the stack trace of the last (ideally of all) go routine (the app has multiple go routines) which panicked and recovered and logged only a not much descriptive error message? I don't know which routine recovered. Also, please keep in mind that I will not alter the code of any imported package. This panic happened in some of the imported packages which creates multiple go routines so I need a way to grab the stack trace of the last recovered routine in order to find where it panic.
The short answer is: Not possible but there are exceptions.
Golang has a few stack control methods and types.
You can control the stack levels with runtime/debug/SetTraceback
func SetTraceback(level string)
SetTraceback sets the amount of detail printed by the runtime
inthe traceback it prints before exiting due to an
unrecovered panic or an internal runtime error.
The level argument takes the same values as the GOTRACEBACK
environment variable. For example, SetTraceback("all") ensure
that the program prints all goroutines when it crashes.
See the package runtime documentation for details. If
SetTraceback is called with a level lower than that of the
environment variable, the call is ignored.
You can also print the stack strace with runtime/debug/Stack
func Stack() []byte
Stack returns a formatted stack trace of the goroutine that calls it. It calls runtime.Stack with a large enough buffer to capture the entire trace.
Also you need to understand how the Built-in funct recover works.
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.
func recover() interface{}
Working Example
This example assumes that the package does not call recover (detailed in another section).
Golang Playground Link
package main
import (
"log"
"errors"
"runtime/debug"
"time"
)
func f2() {
panic(errors.New("oops")) // line 11
}
func f1() {
f2() // line 15
}
func main() {
defer func() {
if e := recover(); e != nil {
log.Printf("%s: %s", e, debug.Stack()) // line 20
}
}()
go f1() // line 25
time.Sleep(time.Second * 1)
}
If package calls recover
If the code is recovering from the panic you need to use a debugger or remove the recover to understand what is going on as seen on the example below which demonstrate that recovered panics can not be "recovered" again.
Golang Playground Link
package main
import (
"log"
"errors"
"runtime/debug"
"time"
)
func f2() {
panic(errors.New("oops")) // line 11
}
func f1() {
defer func() {
if e := recover(); e != nil {
log.Printf("internal %s: %s", e, debug.Stack()) // line 20
}
}()
f2() // line 15
}
func main() {
defer func() {
if e := recover(); e != nil {
log.Printf("external %s: %s", e, debug.Stack()) // line 20
} else {
log.Println("Nothing to print")
}
}()
go f1() // line 25
time.Sleep(time.Second * 1)
}
Lesser then two evils
Debug with Delve Or temporaly edit the package so it logs the full message (once understood you can revert the changes).
Also if you find the problem let the package author know so it can be fixed.

Resources