handling SIGINT (ctrl-c) in golang to convert it to a panic

2.6k Views Asked by At

My goal is to have a handler for SIGINT (i.e., Ctrl-C on the CLI) which will allow deferred function calls to run instead of causing a hard exit. The usecase for this is in a test suite with very long-running tests, I want the CLI user to be able to trigger test cleanup early using Ctrl-C if they want. The test cleanup functions should all be on the deferred function stacks of each of the test functions, so demoting SIGINT to a panic should, in my mind, cause those cleanup functions to run.

The code below is my attempt to do that. If you run this with go run ., you'll see

$ go run .
regular action ran!
post-Sleep action ran!
deferred action ran!

But if you interrupt it during the 5 seconds of sleep, you'll see this instead:

regular action ran!^Cpanic: interrupt

goroutine 8 [running]:
main.panic_on_interrupt(0xc00007c180)
        /home/curran/dev/test/main.go:12 +0x5e
created by main.main
        /home/curran/dev/test/main.go:20 +0xab
exit status 2

I added the interrupt handler and the goroutine because I thought that would de-escalate the SIGINT into a panic and allow the call to fmt.Printf("deferred action ran!") to execute. However, that did not end up being the case.

Here's the code in question:

package main

import (
        "fmt"
        "time"
        "os"
        "os/signal"
)

func panic_on_interrupt(c chan os.Signal) {
        sig := <-c
        panic(sig)
}

func main() {
        c := make(chan os.Signal, 1)
        // Passing no signals to Notify means that
        // all signals will be sent to the channel.
        signal.Notify(c, os.Interrupt)
        go panic_on_interrupt(c)

        fmt.Printf("regular action ran!")
        defer fmt.Printf("deferred action ran!")
        time.Sleep(5 * time.Second)
        fmt.Printf("post-Sleep action ran!")
}                                                                                                                                                                                                                                             
1

There are 1 best solutions below

0
Sergey Sarbash On
func main() {
    fmt.Printf("regular action ran!")
    defer fmt.Printf("deferred action ran!")

    go startLongRunningTest()
    defer longRunningTestCleanupCode()

    //time.Sleep(5 * time.Second)
    //fmt.Printf("post-Sleep action ran!")

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    <-c
}                                                                                                                                                                                                                                             

time.Sleep() blocks running goroutine for the specified time.
You may defer() cleanup code.
Also, you can run time-consuming tests in separate goroutines instead of panicking there.
Avoid use of panic() if possible.