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

SignalsGoSigterm

Signals Problem Overview


I want to capture the Ctrl+C (SIGINT) signal sent from the console and print out some partial run totals.

Signals Solutions


Solution 1 - Signals

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.

Solution 2 - Signals

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
    }
}

Checkout in the Playground

Solution 3 - Signals

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)
....

Solution 4 - Signals

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)                                                     
  }                                                                
}()    

                                                        

Solution 5 - Signals

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

Solution 6 - 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)

}

Solution 7 - Signals

You can have a different goroutine that detects syscall.SIGINT and syscall.SIGTERM signals and relay them to a channel using signal.Notify. You can send a hook to that goroutine using a channel and save it in a function slice. When the shutdown signal is detected on the channel, you can execute those functions in the slice. This can be used to clean up the resources, wait for running goroutines to finish, persist data, or print partial run totals.

I wrote a small and simple utility to add and run hooks at shutdown. Hope it can be of help.

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

You can do this in a 'defer' fashion.

example for shutting down a server gracefully :

srv := &http.Server{}

go_shutdown_hook.ADD(func() {
	log.Println("shutting down server")
	srv.Shutdown(nil)
	log.Println("shutting down server-done")
})
 
l, err := net.Listen("tcp", ":3090")

log.Println(srv.Serve(l))

go_shutdown_hook.Wait()

Solution 8 - Signals

This is another version which work in case you have some tasks to cleanup. Code will leave clean up process in their method.

package main

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



func main() {
	
	_,done1:=doSomething1()
	_,done2:=doSomething2()
 
	//do main thread
	
	
	println("wait for finish")
	<-done1
	<-done2
	fmt.Print("clean up done, can exit safely")
	
}

func doSomething1() (error, chan bool) {
	//do something
	done:=make(chan bool)
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		//cleanup of something1
		done<-true
	}()
	return nil,done
}


func doSomething2() (error, chan bool) {
	//do something
	done:=make(chan bool)
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		//cleanup of something2
		done<-true
	}()
	return nil,done
}

in case you need to clean main function you need to capture signal in main thread using go func() as well.

Solution 9 - Signals

Death is a simple library that uses channels and a wait group to wait for shutdown signals. Once the signal has been received it will then call a close method on all of your structs that you want to cleanup.

Solution 10 - Signals

Just for the record if somebody needs a way to handle signals on Windows.

I had a requirement to handle from program A calling program B through os/exec but program B never was able to terminate gracefully because sending signals through cmd.Process.Signal(syscall.SIGTERM) or other signals are not supported on Windows.

I handled this problem by creating a temp file as a signal. For example, program A creates file .signal.term and program B needs to check if that file exists on interval base. If file exists it will exit the program and handle a cleanup if needed.

I'm sure there are other ways but this did the job.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSebasti&#225;n GrignoliView Question on Stackoverflow
Solution 1 - SignalsLily BallardView Answer on Stackoverflow
Solution 2 - Signalsuser246672View Answer on Stackoverflow
Solution 3 - SignalsadamlamarView Answer on Stackoverflow
Solution 4 - SignalsgravitronView Answer on Stackoverflow
Solution 5 - SignalswillscriptedView Answer on Stackoverflow
Solution 6 - Signalsdılo sürücüView Answer on Stackoverflow
Solution 7 - SignalsAnkit AroraView Answer on Stackoverflow
Solution 8 - SignalsHlexView Answer on Stackoverflow
Solution 9 - SignalsBen AldrichView Answer on Stackoverflow
Solution 10 - Signalsuser212806View Answer on Stackoverflow