More and more people are developing applications using Go, most of which use context operations, such as making HTTP calls, fetching data from a database, or performing asynchronous operations with Go-Routines. The most common use is to pass common data that can be used by all downstream operations.
Why do we need to interrupt operations?
Normally we should make a request via HTTP, then query the database and return the data via HTTP request
If everything were perfect, the sequence diagram would look something like this:
But what happens if the user cancels the request in the middle? This can happen, for example, if the client requests to close the browser in the middle. If not cancelled, the application server and database will continue working and resources will be wasted:
Ideally, if we know that the process (in this case, the HTTP request) is stopped, we want all downstream components of the process to be stopped:
Context interrupts in Go:
Now that we know why interrupts are needed, let’s look at how to implement them in Go.
We typically need to implement interrupts in one of two ways:
- Listen to interrupt
- A break
So let’s look at the Context first
- // context
- type Context interface {
- Done() <-chan struct{}
- Err() error
- Deadline() (deadline time.Time, ok bool )
- Value(key interface{}) interface {}
- }
The context, the context interface
- Methods in the context package are thread-safe and can be used by multiple threads
- Done returns a closed channel when Context is canceled or timeout
- After the Done channel is closed, Err represents the reason for the closure
- If so, Deadline returns the time when the Context will close
- Value returns the Value associated with the key if it exists, nil if it does not
Listening interrupt:
Let’s implement a two-second timeout HTTP service. If the request is cancelled before then, we return the result immediately:
- func main() {
- // Create an HTTP server that listens on port 8000
- http.ListenAndServe(“:8000”, http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- // This prints to STDOUT to show that processing has started
- fmt.Fprint(os.Stdout, “processing request\n”)
- // We use `select` to execute a peice of code depending on which
- // channel receives a message first
- select {
- case <-time.After(2 * time.Second):
- // If we receive a message after 2 seconds
- // that means the request has been processed
- // We then write this as the response
- w.Write([]byte( “request processed”))
- case <-ctx.Done():
- // If the request gets cancelled, log it
- // to STDERR
- fmt.Fprint(os.Stderr, “request cancelled\n”)
- }
- }))
- }
Run the code above and you will see “Request Cancelled” if you close your browser within two seconds.
A break
If you need to cancel an operation, you must issue an interrupt operation from the context. We can do this using the WithCancel function in the context package, assuming we have two dependency operations requested at a time. In this context, “dependence” means that if one person fails, there is no point in the other completing. In this case, if we say that one of the operations fails, we interrupt all operations.
- func operation1(ctx context.Context) error {
- // Let’s assume that this operation failed for some reason
- // We use time.Sleep to simulate a resource intensive operation
- time.Sleep(100 * time.Millisecond)
- return errors.New(“failed”)
- }
- func operation2(ctx context.Context) {
- // We use a similar pattern to the HTTP server
- // that we saw in the earlier example
- select {
- case <-time.After(500 * time.Millisecond):
- fmt.Println(“done”)
- case <-ctx.Done():
- fmt.Println(“halted operation2”)
- }
- }
- func main() {
- // Create a new context
- ctx := context.Background()
- // Create a new context, with its cancellation function
- // from the original context
- ctx, cancel := context.WithCancel(ctx)
- // Run two operations: one in a different go routine
- go func() {
- err := operation1(ctx)
- // If this operation returns an error
- // cancel all operations using this context
- if err ! = nil {
- cancel()
- }
- } ()
- // Run operation2 with the same context we use for operation1
- operation2(ctx)
- }
Time-based interrupts
In many cases, our services have to meet SLA requirements. We may need to interrupt based on timeout! Of course we can also use in RPC requests and DB operations!
- // The context will be cancelled after 3 seconds
- // If it needs to be cancelled earlier, the `cancel` function can
- // be used, like before
- ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
- // The context will be cancelled on 2009-11-10 23:00:00
- ctx, cancel := context.WithDeadline(ctx, time.Date(2009 , time.November, 10, 23, 0, 0 , 0, time.UTC))
For example, make an HTTP API call to an external service. If the request takes too long, it is best to fail early and cancel the request:
- func main() {
- // Create a new context
- // With a deadline of 100 milliseconds
- ctx := context.Background()
- ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond )
- // Make a request, that will call the google homepage
- req, _ := http.NewRequest(http.MethodGet, “http://google.com”, nil )
- // Associate the cancellable context we just created to the request
- req = req.WithContext(ctx)
- // Create a new HTTP client and execute the request
- client := &http.Client{}
- res, err := client.Do(req)
- // If the request failed, log to STDOUT
- if err ! = nil {
- fmt.Println(“Request failed:”, err)
- return
- }
- // Print the statuscode if the request succeeds
- fmt.Println(“Response received, status code:”, res.StatusCode)
- }
Conclusion:
All context’s parent object, the root object, is an empty context, it can’t be canceled, it has no value, it never gets canceled, it doesn’t have a timeout, it always exists as the top-level context for processing requests, Then create child objects with WithCancel and WithTimeout functions to obtain cancel and timeout capabilities
When the top-level request function ends, we can cancel a context to signal that the other routine ends
The WithValue method can add a key-value pair to the context that a different routine gets
Your support will encourage us to continue to create!
WeChat pay
Alipay
Scan the QR code with wechat to tip
Scan the QR code with Alipay
WeChat
Sina Weibo
Qzone
Evernote
Facebook
Twitter
Email
Telegram
share