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:

  1. Listen to interrupt
  2. A break

So let’s look at the Context first

  1. // context
  2. type Context interface {
  3. Done() <-chan struct{}
  4. Err() error
  5. Deadline() (deadline time.Time, ok bool )
  6. Value(key interface{}) interface {}
  7. }



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:

  1. func main() {
  2. // Create an HTTP server that listens on port 8000
  3. http.ListenAndServe(“:8000”, http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) {
  4. ctx := r.Context()
  5. // This prints to STDOUT to show that processing has started
  6. fmt.Fprint(os.Stdout, “processing request\n”)
  7. // We use `select` to execute a peice of code depending on which
  8. // channel receives a message first
  9. select {
  10. case <-time.After(2 * time.Second):
  11. // If we receive a message after 2 seconds
  12. // that means the request has been processed
  13. // We then write this as the response
  14. w.Write([]byte( “request processed”))
  15. case <-ctx.Done():
  16. // If the request gets cancelled, log it
  17. // to STDERR
  18. fmt.Fprint(os.Stderr, “request cancelled\n”)
  19. }
  20. }))
  21. }



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.

  1. func operation1(ctx context.Context) error {
  2. // Let’s assume that this operation failed for some reason
  3. // We use time.Sleep to simulate a resource intensive operation
  4. time.Sleep(100 * time.Millisecond)
  5. return errors.New(“failed”)
  6. }
  7. func operation2(ctx context.Context) {
  8. // We use a similar pattern to the HTTP server
  9. // that we saw in the earlier example
  10. select {
  11. case <-time.After(500 * time.Millisecond):
  12. fmt.Println(“done”)
  13. case <-ctx.Done():
  14. fmt.Println(“halted operation2”)
  15. }
  16. }
  17. func main() {
  18. // Create a new context
  19. ctx := context.Background()
  20. // Create a new context, with its cancellation function
  21. // from the original context
  22. ctx, cancel := context.WithCancel(ctx)
  23. // Run two operations: one in a different go routine
  24. go func() {
  25. err := operation1(ctx)
  26. // If this operation returns an error
  27. // cancel all operations using this context
  28. if err ! = nil {
  29. cancel()
  30. }
  31. } ()
  32. // Run operation2 with the same context we use for operation1
  33. operation2(ctx)
  34. }



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!

  1. // The context will be cancelled after 3 seconds
  2. // If it needs to be cancelled earlier, the `cancel` function can
  3. // be used, like before
  4. ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
  5. // The context will be cancelled on 2009-11-10 23:00:00
  6. 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:

  1. func main() {
  2. // Create a new context
  3. // With a deadline of 100 milliseconds
  4. ctx := context.Background()
  5. ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond )
  6. // Make a request, that will call the google homepage
  7. req, _ := http.NewRequest(http.MethodGet, “http://google.com”, nil )
  8. // Associate the cancellable context we just created to the request
  9. req = req.WithContext(ctx)
  10. // Create a new HTTP client and execute the request
  11. client := &http.Client{}
  12. res, err := client.Do(req)
  13. // If the request failed, log to STDOUT
  14. if err ! = nil {
  15. fmt.Println(“Request failed:”, err)
  16. return
  17. }
  18. // Print the statuscode if the request succeeds
  19. fmt.Println(“Response received, status code:”, res.StatusCode)
  20. }



 

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

 

To play to admire


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