Читать книгу Practical Go - Amit Saha - Страница 64

Handling User Signals

Оглавление

We touched upon the fact that a number of standard library functions accept a context as a parameter. Let's see how it works using the os/exec package's execCommandContext() function. One situation in which this becomes useful is when you want to enforce a maximum time of execution for these commands. Once again, this can be implemented by using a context created via the WithTimeout() function:

package main import ( "context" "fmt" "os" "os/exec" "time" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := exec.CommandContext(ctx, "sleep", "20").Run(); err != nil { fmt.Fprintln(os.Stdout, err) }

}

When run on Linux/MacOS, the above code snippet will yield the following error:

signal: killed

The CommandContext() function force kills an external program when the context expires. In the above code, we set up a context that will be canceled after 10 seconds. We then used the context to execute a command "sleep", "20", which will sleep for 20 seconds. Hence the command is killed. Thus, in a scenario where you want your application to execute external commands but want to have a guaranteed behavior that the commands must finish execution in a certain amount of time, you can achieve it using the technique above.

Next let's look at introducing another point of control in the program—the user. User signals are a way for the user to interrupt the normal workflow of a program. Two common user signals on Linux and MacOS are SIGINT when the Ctrl+C key combination is pressed and SIGTERM when the kill command is executed. We want to allow the user to be able to cancel this external program at any point of time if the time-out hasn't already expired using either the SIGINT or SIGTERM signal.

Here are the steps involved in doing this:

1 Create a context using the WithTimeout() function.

2 Set up a signal handler that will create a handler for the SIGINT and SIGTERM signal. When one of the signals is received, the signal handling code will manually call the cancellation function returned in step 1.

3 Execute the external program using the CommandContext() function using the context created in step 1.

Step 1 is implemented in a function, createContextWithTimeout() :

func createContextWithTimeout(d time.Duration) (context.Context, context.CancelFunc) { ctx, cancel := context.WithTimeout(context.Background(), d) return ctx, cancel }

The WithTimeout() function from the context package is called to create a context that is canceled when a specified unit of time, d, expires. The first parameter is an empty non- nil context created via a call to the context.Background() function. The context, ctx, and the cancellation function, cancel, are returned. We do not call the cancellation function here since we need the context to be around for the lifetime of the program.

Step 2 is implemented in the setupSignalHandler() function:

func setupSignalHandler(w io.Writer, cancelFunc context.CancelFunc) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) go func() { s := <-c fmt.Fprintf(w, "Got signal:%v\n", s) cancelFunc() }() }

This function constructs a way to handle the SIGINT and SIGTERM signals. A channel of capacity 1 is created with the type Signal (defined in the os package). Then we call the Notify() function from the signal package essentially to set up a listening channel for the syscall.SIGINT and syscall.SIGTERM signals. We set up a goroutine to wait for this signal. When we get one, we call the cancelFunc() function, which is the context cancellation function corresponding to the ctx created above. When we call this function, the implementation of os.execCommandContext() recognizes this and eventually force kills the command. Of course, if no SIGINT or SIGTERM signal is received, the command is allowed to execute normally according to the defined context, ctx .

Step 3 is implemented by the following function:

func executeCommand(ctx context.Context, command string, arg string) error { return exec.CommandContext(ctx, command, arg).Run() }

The complete program is shown in Listing 2.8.

Practical Go

Подняться наверх