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.
Listing 2.8: Handling user signals
// chap2/user-signal/main.go package main import ( "context" "fmt" "io" "os" "os/exec" "os/signal" "time" ) // TODO Insert definition of createContextWithTimeout() as above // TODO Insert definition of setupSignalHandler() as above // TODO Insert definition of executeCommand as above func main() { if len(os.Args) != 3 { fmt.Fprintf(os.Stdout, "Usage: %s <command> <argument>\n", os.Args[0]) os.Exit(1) } command := os.Args[1] arg := os.Args[2] // Implement Step 1 cmdTimeout := 30 * time.Second ctx, cancel := createContextWithTimeout(cmdTimeout) defer cancel() // Implement Step 2 setupSignalHandler(os.Stdout, cancel) // Implement Step 3 err := executeCommand(ctx, command, arg) if err != nil { fmt.Fprintln(os.Stdout, err) os.Exit(1) } }
The main()
function starts by checking to see if the expected number of arguments have been specified. Here we implement a basic user interface and expect the application to be executed as ./application sleep 60
where sleep
is the command to be executed and 60
is the argument to the command. Then we store the command to be executed and the argument to it in two string variables: – command
and arg
. The createContextWithTimeout()
function is then called with a duration object specifying a 30-second time-out. The function returns a context, ctx
, and a context cancellation function, cancel
. In the next statement, we call the function in a deferred call.
We then call the setupSignalHandler()
function, passing it two parameters: – os.Stdout
and the context's cancellation function, cancel
.
Finally, we call the executeCommand()
function with the context object created, ctx
; the command to execute, command
; and the argument to the command, arg
. If there is an error returned, it is printed.
Create a new directory, chap2/user-signal,
and initialize a module inside it:
$ mkdir -p chap2/user-signal $ cd chap2/user-signal $ go mod init github.com/username/user-signal
Next, save Listing 2.8 as a new file, main.go
, and build it:
$ go build -o application
Considering that the time-out is set to 30 seconds, let's try executing the sleep
command with a value for the time to sleep:
% ./application sleep 60 ^CGot signal:interrupt signal: interrupt
We ask the sleep
command to sleep for 60 seconds but manually abort it by pressing Ctrl+C. The error message tells us how the command was aborted.
Next, we sleep for 10 seconds:
% ./application sleep 10
As 10 seconds is lower than the context time-out for 30 seconds, it exits cleanly. Finally, let's execute the sleep
command for 31 seconds:
% ./listing7 sleep 31 signal: killed
Now we can see that the time-out context kicks in and kills the process.
Summary
In this chapter, you learned about patterns for implementing scalable command-line applications. You learned how to implement a sub-command-based interface for your application, and you built upon it to design a scalable architecture for applications with sub-commands. Then you learned to use the context
package to implement certain control over the runtime behavior of your applications. Finally, you used goroutines and channels to allow the user to interrupt the application using contexts and signals.
In the next chapter, we will continue our exploration into the world of writing command-line applications as you learn about writing HTTP clients. You will do so as you build out the HTTP client implementation for which we laid the foundation in this chapter.
Конец ознакомительного фрагмента.
Текст предоставлен ООО «ЛитРес».
Прочитайте эту книгу целиком, купив полную легальную версию на ЛитРес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.