This is the fifth day of my participation in the August More text Challenge. For details, see:August is more challenging

Go coroutine timeout control

  • Select Blocking mode
  • The Context way

Here’s a scenario:

Suppose that service A in the business needs to invoke service B, requiring A 5s timeout, then how to gracefully implement?

Select timeout Control

Consider whether you can use select + time.after

The main use here is the communication characteristics of the channel between Ctrip. When the program is successfully invoked, it will send signals to the channel. The channel will block until the call succeeds.

select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(time.Second * 3):
		fmt.Println("timeout 2")}Copy the code

Case res := -c2; case res := -c2; case res := -c2; case res := -c2; case res := -c2; This allows you to control the timeout of 3s.

Res := <-c2 because channel can block, time.After can block.

Look at the After source. Sleep. go is also a channel

func After(d Duration) <-chan Time {
	return NewTimer(d).C
}
Copy the code

Examples of complete code:

package timeout

import (
	"fmt"
	"testing"
	"time"
)

func TestSelectTimeOut(t *testing.T) {
	// In this example, suppose we execute an external call and write the result to C1 2 seconds later
	c1 := make(chan string.1)
	go func(a) {
		time.Sleep(time.Second * 2)
		c1 <- "result 1"} ()// Here select is used to implement the timeout, 'res := <-c1' wait for the channel result,
	// '< -time.After' returns a value After waiting 1 second, because select is the first
	// Execute cases that are no longer blocking, so the timeout program will be executed if
	// 'res := <-c1' for more than 1 second
	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(time.Second * 1):
		fmt.Println("timeout 1")}// If we set the timeout to 3 seconds, at this time 'res := <-c2' will be
	// Execute before the timeout case, so that the output can be written to channel C2
	c2 := make(chan string.1)
	go func(a) {
		time.Sleep(time.Second * 2)
		c2 <- "result 2"} ()select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(time.Second * 3):
		fmt.Println("timeout 2")}}Copy the code

Running result:

=== RUN TestSelectTimeOut timeout 1 result 2 -- PASS: TestSelectTimeOut (3.00s) PASSCopy the code

Go timer

This is a similar implementation of the timer, but it also sends data through the channel.

Package main import "time" import "FMT" func main() {// Ticker uses a similar mechanism to Timer, using a channel to send data. // Here we use the range function to traverse the channel data, which is sent every 500 milliseconds. () {for t := range ticker.C {fmt.println ("Tick ") At ", t)}}() // The Ticker can be stopped just like the Timer. Once the Ticker stops, the channel will no longer receive data. () return (" ticker stopped") {return (" ticker stopped") () return (" ticker stopped")}Copy the code

go context

Context listens for IO operations, begins to read network requests from the current connection, and whenever a request is read, the cancelCtx is passed in to transmit a cancellation signal. It can send a cancellation signal to cancel all network requests in progress.

		go func(ctx context.Context, info *Info) {
			timeLimit := 120
			timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeLimit)*time.Millisecond)
			defer func() {
				cancel()
				wg.Done()
			}()
			resp := DoHttp(timeoutCtx, info.req)
		}(ctx, info)
Copy the code

Resp := DoHttp(timeoutCtx, info.req) The business code contains the HTTP call NewRequestWithContext

req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(paramString))
Copy the code

When DoHttp(timeoutCtx, info.req) processing time exceeds the timeout period, it will automatically expire and print context Deadline exceeded.

Look at this code:

package main import ( "context" "fmt" "testing" "time" ) func TestTimerContext(t *testing.T) { now := time.Now() later, _ := time.ParseDuration("10s") ctx, cancel := context.WithDeadline(context.Background(), now.Add(later)) defer cancel() go Monitor(ctx) time.Sleep(20 * time.Second) } func Monitor(ctx context.Context) { select  { case <-ctx.Done(): fmt.Println(ctx.Err()) case <-time.After(20 * time.Second): fmt.Println("stop monitor") } }Copy the code

Running result:

=== RUN TestTimerContext Context deadline exceeded -- PASS: TestTimerContext (20.00s) PASSCopy the code

Context interfaces are as follows:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
Copy the code
  • Deadline – Returns the context. The time that the context was cancelled, that is, the Deadline by which the work was completed;
  • Done – returns a Channel that will be closed when the current work is completed or the context is cancelled. Multiple calls to the Done method will return the same Channel;
  • Err — returns context. The reason for the end of context, which returns a non-null value only if the Channel returned by Done is closed;
    • If context. context is Canceled, a cancellation error is returned.
    • If context. context times out, a DeadlineExceeded error will be returned;
  • Value – gets the Value of the Key from context. context. Calling Value multiple times with the same Key will return the same result for the same context. This method can be used to pass request-specific data;