The Golang function signal.Notify enables a program to respond to process signals such as SIGINT , SIGKILL , etc. with registered event handlers. However, a Golang developer should take care to avoid trapping SIGABRT , and this blog explains why.

The following, short program demonstrates why trapping SIGABRT is a bad idea:

package main import ( "fmt" "os" "os/signal" "strings" "syscall" ) func main() { // Print the process ID to make it easier to send this program a signal. fmt.Printf("pid: %d

", os.Getpid()) // If this program's first argument is "trap" then trap SIGABRT. if len(os.Args) > 1 && strings.EqualFold(os.Args[1], "trap") { n := make(chan os.Signal, 1) signal.Notify(n, syscall.SIGABRT) go func() { for sig := range n { fmt.Println(sig) os.Exit(1) } }() } // Use a channel to block the program by waiting indefinitely // until something reads the channel (which will never happen). c := make(chan struct{}) <-c }

The last two lines of the above program ensure that the process will never exit — not unless it is sent a signal to do so. For example:

$ go run main.go pid: 14755

The program prints its process ID and then waits for a signal, either via the kill command or, if the program is waiting in the foreground as the one above is, via CTRL-C :

$ go run main.go pid: 14755 ^Csignal: interrupt

Sending a signal that causes a program to exit is nothing new, however:

Golang programs interpret the SIGABRT signal as a special event that forces a core dump, including all goroutines and if they’re deadlocked.

That last bit of information will be on the test :)

For example, the above program will be executed once more, but this time instead of CTRL-C used to send the SIGINT signal to the program, a separate terminal session will be used to send the SIGABRT signal:

$ go run main.go pid: 14765

Send the SIGABRT signal from a separate terminal:

$ kill -SIGABRT 14765

Now go look at the original terminal session:

$ go run main.go pid: 14765 SIGABRT: abort PC=0x105497b m=0 sigcode=0 goroutine 0 [idle]: runtime.mach_semaphore_wait(0x803, 0x1191690, 0x0, 0x7ffeefbfe840, 0x101ff0a, 0x114b560, 0x7ffeefbfe840, 0x104f4d3, 0xffffffffffffffff, 0x11915f8, ...) /Users/akutz/.go/1.9/src/runtime/sys_darwin_amd64.s:445 +0xb runtime.semasleep1(0xffffffffffffffff, 0x11915f8) /Users/akutz/.go/1.9/src/runtime/os_darwin.go:413 +0x52 runtime.semasleep.func1() /Users/akutz/.go/1.9/src/runtime/os_darwin.go:432 +0x33 runtime.systemstack(0x7ffeefbfe868) /Users/akutz/.go/1.9/src/runtime/asm_amd64.s:360 +0xab runtime.semasleep(0xffffffffffffffff, 0x1) /Users/akutz/.go/1.9/src/runtime/os_darwin.go:431 +0x44 runtime.notesleep(0x114bdb0) /Users/akutz/.go/1.9/src/runtime/lock_sema.go:167 +0xe9 runtime.stopm() /Users/akutz/.go/1.9/src/runtime/proc.go:1670 +0xe5 runtime.findrunnable(0xc420020000, 0x0) /Users/akutz/.go/1.9/src/runtime/proc.go:2125 +0x4d2 runtime.schedule() /Users/akutz/.go/1.9/src/runtime/proc.go:2245 +0x12c runtime.park_m(0xc420000180) /Users/akutz/.go/1.9/src/runtime/proc.go:2308 +0xb6 runtime.mcall(0x7ffeefbfea50) /Users/akutz/.go/1.9/src/runtime/asm_amd64.s:286 +0x5b goroutine 1 [chan receive]: main.main() /Users/akutz/Projects/go/src/github.com/akutz/sigabrt/main.go:30 +0xe8 goroutine 5 [syscall]: os/signal.signal_recv(0x0) /Users/akutz/.go/1.9/src/runtime/sigqueue.go:131 +0xa7 os/signal.loop() /Users/akutz/.go/1.9/src/os/signal/signal_unix.go:22 +0x22 created by os/signal.init.0 /Users/akutz/.go/1.9/src/os/signal/signal_unix.go:28 +0x41 rax 0xe rbx 0x114bca0 rcx 0x7ffeefbfe7e0 rdx 0x7ffeefbfe868 rdi 0x803 rsi 0x1 rbp 0x7ffeefbfe818 rsp 0x7ffeefbfe7e0 r8 0xc42005a058 r9 0xc420000208 r10 0xc42001e160 r11 0x286 r12 0xc42001e158 r13 0x10cdaaf r14 0xa r15 0x1 rip 0x105497b rflags 0x286 cs 0x7 fs 0x0 gs 0x0 exit status 2

The above core dump reveals that the program was waiting perpetually:

goroutine 1 [chan receive]: main.main() /Users/akutz/Projects/go/src/github.com/akutz/sigabrt/main.go:30 +0xe8

This type of information is extraordinarily useful when developing and debugging Go programs. And it’s this type of information that is completely lost if SIGABRT is trapped by the signal.Notify function:

$ go run main.go trap pid: 14779

$ $ kill -SIGABRT 14779

$ go run main.go trap pid: 14779 abort trap exit status 1

As illustrated above, when the example program traps the SIGABRT signal the core dump is no longer emitted to STDERR , and there is currently no way via Go code to emit a core dump manually (of which this author is aware or was able to find).

Hopefully this short blog post helps Go developers to understand why they should try to avoid trapping SIGABRT . The value potentially removed from the debugging process is simply too great.