Skip to content
shellhacks.dev
Go

Handle Ctrl-C (SIGINT) in a Go Program with NotifyContext

Cancel work on Ctrl-C with signal.NotifyContext, which returns a context that ends on SIGINT or SIGTERM. Call stop afterward to restore defaults. Go 1.16 or later.

1 min read
Summarize in AI

A long-running Go program should shut down cleanly when it receives an interrupt.

Go
package main

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

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	go func() {
		time.Sleep(200 * time.Millisecond)
		syscall.Kill(os.Getpid(), syscall.SIGINT)
	}()

	select {
	case <-ctx.Done():
		fmt.Println("interrupted:", ctx.Err())
	case <-time.After(5 * time.Second):
		fmt.Println("no signal")
	}
	stop()
	fmt.Println("default handling is back; a second Ctrl-C would now kill the process")
}
Text
interrupted: context canceled
default handling is back; a second Ctrl-C would now kill the process

signal.NotifyContext(parent, signals...) returns a derived context that is canceled when one of the signals arrives, so the same select on ctx.Done() that handles timeouts also handles Ctrl-C. The program sends itself SIGINT after 200 milliseconds and the output shows interrupted: context canceled. Calling stop() unregisters the handler, and from then on a second signal ends the process in the usual way. See signal.NotifyContext.

syscall.Kill is Unix only; on Windows only os.Interrupt is delivered. The context says that a signal arrived, not which one, so use signal.Notify with a channel when the signal number matters. Always call stop, or the handler stays registered for the life of the process.

Taggedgosignals
Share

More snippets

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.