Implementation of go co process timeout control

Time:2021-10-20

Go co process timeout control

  • Select blocking mode
  • Context mode

Let’s start with a scenario:

Suppose that service a needs to call service B in the business and requires a 5S timeout to be set, how to implement it gracefully?

Select timeout control

Consider whether it can be implemented in the mode of select + time. After

The main use here is the characteristics of communication between channels in Ctrip. When the program call is successful, it will send a signal to the channel. Before the call is successful, the channel will be blocked.

?
1
2
3
4
5
6
select {
 case res := <-c2:
  fmt.Println(res)
 case <-time.After(time.Second * 3):
  fmt.Println("timeout 2")
 }

When there is data in the C2 channel and the timeout time does not reach 3S, go through the business logic of case res: = < – C2. When the timeout time reaches 3S, go through the business logic of case < – time. After (time. Second * 3), so as to realize the control of timeout for 3S.

Res: = < – C2 is because the channel can be blocked, so why can time. After be blocked?

Look at the after source code. Sleep.go can see that it is actually a channel

?
1
2
3
func After(d Duration) <-chan Time {
 return NewTimer(d).C
}

Complete code example:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 after 2 seconds
 c1 := make(chan string, 1)
 go func() {
  time.Sleep(time.Second * 2)
  c1 <- "result 1"
 }()
 //Here, select is used to achieve timeout, ` res: = < - C1 ` wait for channel results,
 //` < - time. After ` returns a value after waiting for 1 second, because select first
 //Execute those cases that are no longer blocked, so the timeout program will be executed here. If
 //` res: = < - C1 ` if it is not executed 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, 'res: = < - C2' will
 //Execute before timeout case, so that the value written to channel C2 can be output
 c2 := make(chan string, 1)
 go func() {
  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")
 }
}

Operation results:

=== RUN   TestSelectTimeOut
timeout 1
result 2
— PASS: TestSelectTimeOut (3.00s)
PASS

Go timer timer

This is a timer implementation similar to timer. It is also used to send data through channels.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "time"
import "fmt"
func main() {
  //Ticker uses a mechanism similar to timer. It also uses a channel to send data.
  //Here, we use the range function to traverse the channel data, which is updated every 500 milliseconds
  //Send it once so that we can receive it
  ticker := time.NewTicker(time.Millisecond * 500)
  go func() {
    for t := range ticker.C {
    fmt.Println("Tick at", t)
    }
  }()
  //Ticker can be stopped like timer. Once the ticker stops, the channel will no longer
  //Receive data, here we will stop after 1500 milliseconds
  time.Sleep(time.Millisecond * 1500)
  ticker.Stop()
  fmt.Println("Ticker stopped")
}

go context

Context monitors whether there is an IO operation and starts reading network requests from the current connection. Whenever a request is read, the cancelctx will be passed in to pass the cancellation signal. You can send a cancellation signal to cancel all ongoing network requests.

?
1
2
3
4
5
6
7
8
9
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)

The key depends on the business code: resp: = dohttp (timeoutctx, info. Req) the business code contains HTTP calling newrequestwithcontext

?
1
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(paramString))

The above code sets the expiration time. When the dohttp (timeoutctx, info. Req) processing time exceeds the timeout time, it will automatically expire and print the context deadline exceeded.

Look at a code:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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")
 }
}

Operation results:

=== RUN   TestTimerContext
context deadline exceeded
— PASS: TestTimerContext (20.00s)
PASS

The context interface is as follows:

?
1
2
3
4
5
6
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline – returns the time when the context. Context is cancelled, that is, the deadline for completing the work;
  • Done – returns a channel, which will be closed after the current work is completed or the context is cancelled. Calling the done method multiple times will return the same channel;
  • Err – return the reason for the end of context. Context. It will only return a non empty value when the channel returned by done is closed;
    • If the context.context is cancelled, the cancelled error will be returned;
    • If context.context times out, deadlineexceeded error will be returned;
  • Value – get the value corresponding to the key from context.context. For the same context, calling value multiple times and passing in the same key will return the same result. This method can be used to transfer specific data of the request;

This is the end of this article on the implementation of go process timeout control. For more information about go process timeout control, please search for previous articles on developeppaper or continue to browse the following articles. I hope you will support developeppaper in the future!