Skip to content

Instantly share code, notes, and snippets.

@ajitid
Last active March 29, 2023 20:53
Show Gist options
  • Save ajitid/b22dcf46b62afb196805954e5a638f8c to your computer and use it in GitHub Desktop.
Save ajitid/b22dcf46b62afb196805954e5a638f8c to your computer and use it in GitHub Desktop.
Wait forever so that your goroutines can finish in Golang
/*
NOT TO BE USED IN PRODUCTION
Lets you wait forever so that your goroutines can finish.
Works great when coupled with [watchexec](https://watchexec.github.io/) like `watchexec -rc go run .` while learning Go.
src: https://stackoverflow.com/questions/36419054/go-projects-main-goroutine-sleep-forever#comment132984686_36419288
Should be used like:
```go
func main() {
// your code
waitForever()
}
```
Do not do `defer waitForever()` otherwise you would be blocking the panic to occur (and only terminating the program with <ctrl-c> would make it occur, not ideal).
Using a blocking channel with `defer` is not a good idea in general.
*/
package main
import (
"os"
"os/signal"
"syscall"
)
func waitForever() {
exitSignal := make(chan os.Signal, 1)
signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM)
<-exitSignal
}
/*
NOT TO BE USED IN PRODUCTION
Lets you wait forever so that your goroutines can finish.
Works great when coupled with [watchexec](https://watchexec.github.io/) like `watchexec -rc go run .` while learning Go.
src: https://stackoverflow.com/questions/36419054/go-projects-main-goroutine-sleep-forever#comment132984686_36419288
Should be placed in utils/ folder and be used like:
```go
import "your-pkg-name/utils"
func main() {
defer utils.WaitForever()
// rest of your code...
}
```
*/
package utils
import (
"fmt"
"os"
"os/signal"
"runtime/debug"
"strings"
"syscall"
)
func WaitForever() {
// waiting for `exitSignal` would hide panic of current goroutine and would only show it
// when we do <ctrl-c>, so we have to check for any panic ourselves
if e := recover(); e != nil {
fmt.Println("panic:", e)
trace := string(debug.Stack())
// `trace` could include panic's own fn call trace as well, which we'd discard
skipTrace := true
{
tracePerLine := strings.Split(trace, "\n")
for i := 0; i < len(tracePerLine); i++ {
if strings.HasPrefix(tracePerLine[i], "panic(") && strings.Contains(tracePerLine[i+1], "/go/src/runtime/panic.go") {
i += 1
skipTrace = false
continue
}
if skipTrace {
continue
}
fmt.Println(tracePerLine[i])
}
}
// if it is still true, it would mean that haven't logged anything, so we'll log the whole trace
if skipTrace {
fmt.Print(trace)
}
// panic gives this status which is actually incorrect,
// but we'll imitate its behavior anyway, see: https://github.com/golang/go/issues/24284
os.Exit(2)
}
exitSignal := make(chan os.Signal, 1)
signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM)
<-exitSignal
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment