Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to capture a Ctrl+C signal and run a cleanup function, in a "defer" fashion?

You can use the os/signal package to handle incoming signals. Ctrl+C is SIGINT, so you can use this to trap os.Interrupt.

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
    for sig := range c {
        // sig is a ^C, handle it
    }
}()

The manner in which you cause your program to terminate and print information is entirely up to you.


This works:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time" // or "runtime"
)

func cleanup() {
    fmt.Println("cleanup")
}

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        cleanup()
        os.Exit(1)
    }()

    for {
        fmt.Println("sleeping...")
        time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
    }
}

To add slightly to the other answers, if you actually want to catch SIGTERM (the default signal sent by the kill command), you can use syscall.SIGTERM in place of os.Interrupt. Beware that the syscall interface is system-specific and might not work everywhere (e.g. on windows). But it works nicely to catch both:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....

There were (at time of posting) one or two little typos in the accepted answer above, so here's the cleaned up version. In this example I'm stopping the CPU profiler when receiving Ctrl+C.

// capture ctrl+c and stop CPU profiler                            
c := make(chan os.Signal, 1)                                       
signal.Notify(c, os.Interrupt)                                     
go func() {                                                        
  for sig := range c {                                             
    log.Printf("captured %v, stopping profiler and exiting..", sig)
    pprof.StopCPUProfile()                                         
    os.Exit(1)                                                     
  }                                                                
}()    

All of the above seem to work when spliced in, but gobyexample's signals page has a really clean and complete example of signal capturing. Worth adding to this list.

package main

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

func main() {
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}

Source: gobyexample.com/signals


look at the example

When we run this program it will block waiting for a signal. By typing ctrl-C (which the terminal shows as ^C) we can send a SIGINT signal, causing the program to print interrupt and then exit.

signal. Notify registers the given channel to receive notifications of the specified signals.

package main

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

func main() {

    sig := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sig
        fmt.Println()
        fmt.Println(sig)
        done <- true

        fmt.Println("ctrl+c")
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}

detect HTTP request cancel



package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {

    mux := http.NewServeMux()
    mux.HandleFunc("/path", func(writer http.ResponseWriter, request *http.Request) {

        time.Sleep(time.Second * 5)

        select {
        case <-time.After(time.Millisecond * 10):

            fmt.Println("started")
            return
        case <-request.Context().Done():
            fmt.Println("canceled")
        }
    })

    http.ListenAndServe(":8000", mux)

}