SoFunction
Updated on 2025-03-01

How to close the signal by intercepting Golang application gracefully

Golang is not a system-level programming language like C, but it still provides the following features to help developers interact with the underlying operating system, such as the signal (singals), os/singals packages that implement these functions. Compared with other languages ​​that use complex or redundant methods to process OS signals, Golang's built-in OS package provides an easy-to-use method to process Unix signals, which is very direct and simple. In fact, it is usually necessary to deal with single in Unix-like systems. This article introduces the concept of single and illustrates its application scenarios through examples.

Start with the example

Suppose scenario: The Golang application prints a message when it is closed: "Thank you for using Golang." First, create a new main function, and the function body simulates some business until the end command is received.

func main() {
   for {
      ("Doing Work")
      (1 * )
   }
}

When running the application and then sending an execution kill signal (Ctrl + C) from the OS, the output may be similar to this:

Doing Work
Doing Work
Doing Work
Process finished with exit code 2

Now we want to intercept the kill signal and define the processing logic: print the necessary shutdown information.

Receive signal

First, channe is created to receive commands from the operating system. The os package provides a signal interface to process based on OS specification signals.

killSignal := make(chan , 1)

In order to notify killSignal, you need to use the Notify function provided in the signal package. The first parameter is a channel of type Signal, and the next parameter receives a list of signals sent to the channel.

Notify(c chan<- , sig ...)

We can also use the syscall package to notify signals of specific commands:

(c chan<- , , )

To process the signal, we use the killSignal channel in the main function to wait for the interrupt signal. Once a command from the OS is received, the end message is printed and the application is received. Now move the loop code for handling the business into an independent goroute, and here define an anonymous function:

go func() {
   for {
      ("Doing Work")
      (1 * )
   }
}()

Because the business code runs in a standalone routine, the main function implements waiting for the killSignal signal and printing the message before it ends.

<-killSignal
("Thanks for using Golang!")

Complete code

The following are the parts assembled together, and the complete code is as follows:

package main

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

func main() {

	killSignal := make(chan , 1)
	(killSignal, )
	go func() {
		for {
			("Doing Work")
			(1 * )
		}
	}()
	<-killSignal
	("Thanks for using Golang!")
}

Run the program and see that the business loop is executed until the interrupt signal is received, the message is printed and ended:

Doing Work
Doing Work
Doing Work
Thanks for using Golang!

Common signals

For command line programs, pressCTRL+CWhen it is sentSIGINTSignal,SIGINTis the name associated with the specified number of signal. The signal is actually a program-generated software interrupt, and it is also a mechanism for asynchronous processing of events. Usually for safety reasons, signals are generated using names rather than numerical values.

Most signals and their specific actions are defined by the operating system and also perform corresponding functions within the scope of the operating system's permissions. Therefore, being able to handle certain events does not allow users to use the system freely. Some signals will be ignored by the operating system even if they are generated, and the operating system determines which signals the program allows to process.

For example, SIGKILL and SIGSTOP signals can also be captured, blocked, or ignored. Because these signals are crucial to keep the operating system "smooth" functionality, it provides a way for kernel and root users to stop processes under extreme conditions. The number associated with SIGKILL is 9. Linux provides a kill command to send a SIGTERM signal, terminating the running program. You can use the kill -l command to view the complete support signal:

picture

There are many signals available, but not all signals can be processed by the program. Signals like SIGKILL and SIGSTOP cannot be called by programs or ignored by programs. The reason is simple: they are too important to be abused by malicious programs.

System signals are divided into synchronous signals and asynchronous signals. Where the synchronization signal is a signal triggered by an error during program execution, such as SIGBUS, SIGFPE, SIGSEGV. In the Golang program, the synchronization signal is usually converted to a runtime panic. Synchronous signals require event and event time. On the contrary, asynchronous signals do not need to be synchronized to the clock. Asynchronous signals are signals sent by the system core or other programs. For example, SIGHUP means that the terminal it controls is closed, commonly used when pressingCTRL+CWhen it is sentSIGINTSignal. whenSIGINTWhen a signal is generated by a keyboard, it is actually not aborting the application, but a specific keyboard interrupt is generated, and the program that handles the interrupt begins to execute, and its default behavior is closed when the application is closed, but we can override the default behavior and write our own business processing logic.

In fact, many signal processing can be overwritten, and Go developers can write their own handlers to customize signal processing behavior. However, it is not a wise idea to change the default behavior of a signal unless there is a very good reason. Here we list common linux signals:

SIGHUP: ‘HUP’ = ‘hung up’. This signal is generated when a controlling process dies or hangs up detected on the controlling terminal.
SIGINT: When a process is interrupted from keyboard by pressing CTRL+C
SIGQUIT: Quit from keyboard
SIGILL: Illegal instruction. A synonym for SIGPWR() – power failure
SIGABRT: Program calls the abort() function – an emergency stop.
SIGBUS: Bad memory access. Attempt was made to access memory inappropriately
SIGFPE: FPE = Floating point exception
SIGKILL: The kill signal. The process was explicitly killed.
SIGUSR1: This signal is open for programmers to write a custom behavior.
SIGSEGV: Invalid memory reference. In C when we try to access memory beyond array limit, this signal is generated.
SIGUSR2: This signal is open for programmers to write a custom behavior.
SIGPIPE: This signals us open for programmers to write a custom behavior.
SIGALRM: Process requested a wake up call by the operating system such as by calling the alarm() function.
SIGTERM: A process is killed

Process multiple signals

Let’s take a look at another example. Compared with the above example, the processing of SIGTERM signals is added:

package main

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

func handler(signal ) {
	if signal ==  {
		("Got kill signal. ")
		("Program will terminate now.")
		(0)
	} else if signal ==  {
		("Got CTRL+C signal")
		("Closing.")
		(0)
	} else {
		("Ignoring signal: ", signal)
	}
}

func main() {
	sigchnl := make(chan , 1)
	(sigchnl)
    // (sigchnl, , )
	exitchnl := make(chan int)

	go func() {
		for {
			s := <-sigchnl
			handler(s)
		}
	}()

	exitcode := <-exitchnl
	(exitcode)
}

First, create a channel to receive the response signal. We can also specify the signal type: (sigchnl, , ), otherwise all signals will be received, including the command window changing size signal. Then the signal type is determined in the handle function and processed separately.

After the program is run, Linux can view the go program through ps -h and then execute the kill command.

NotifyContext Example

Go.1.16 provides functions that enable more elegant shutdown functions. The following example implements a simple web server. The example handle takes 10s processing time. If the web service is running, the client sends the request through postman or cURL, and then immediately sends the termination signal through ctrl+C on the server.
We want to see the server close gracefully before it terminates by responding to a termination request. If the shutdown is too long, another interrupt signal can be sent to exit immediately, or the pause will start after 5 seconds.

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"
)

var (
	server 
)

func main() {
	// Create context that listens for the interrupt signal from the OS.
	ctx, stop := ((), )
	defer stop()

	server = {
		Addr: ":8080",
	}

	// Perform application startup.
	("/", func(w , r *) {
		( * 10)
		(w, "Hello world!")
	})

	// Listen on a different Goroutine so the application doesn't stop here.
	go ()

	// Listen for the interrupt signal.
	<-()

	// Restore default behavior on the interrupt signal and notify user of shutdown.
	stop()
	("shutting down gracefully, press Ctrl+C again to force")

	// Perform application shutdown with a maximum timeout of 5 seconds.
	timeoutCtx, cancel := ((), 5*)
	defer cancel()

	if err := (timeoutCtx); err != nil {
		(err)
	}
}

Summarize

This article introduces the concept and commonly used signals, and gives several examples of widely used applications, such as gracefully closing the application service and receiving a termination command in a command-line application.

This is the article about the elegant closure of the Golang application. For more related Golang closing content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!