The problem

Some time ago, I found that there was a service interface on the line, and there were always intermittent alarms, sometimes two or three times a day, sometimes there were no alarms all day.

The alarm logic is that an interface asynchronously invokes another HTTP interface, and the INVOCATION of the HTTP interface times out. But I asked the student who is responsible for the HTTP interface, they said that their interface is corresponding to the millisecond level, and the monitoring screenshot, there are pictures and the truth, what else can I say.

However, timeouts do exist, but the request may not reach the other person.

This kind of occasional problem is not good to reproduce, and occasionally an alarm is also very annoying. The first reaction is to solve the problem first, and the idea is simple. Try again after failure.

The solution

Leaving retry policies aside, let’s talk about when retries are triggered.

We can try again when an ERR is thrown on an interface request, but this is difficult to control. If a request goes out and there is no response for more than ten seconds, the coroutine will have to wait for an error to retry, wasting its life

If no result is returned after the specified timeout, retry (this retry is not important).

func AsyncCall(a) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
	defer cancel()
	go func(ctx context.Context) {
		// Send an HTTP request} ()select {
	case <-ctx.Done():
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(time.Millisecond * 900)):
		fmt.Println("timeout!!!")
		return}}Copy the code

instructions

Set context with WithTimeout to 800 milliseconds.

2. The context terminates after 800 milliseconds or after the method is executed, and a signal is sent to channel ctx.Done when the context terminates.

3, some people may ask, you already set the valid time of the context, why add time.after?

This is because the context in this method is declared by itself, and you can manually set the timeout, but in most scenarios, the CTX is passed upstream, and we don’t know how much time is left in the context that’s been passed upstream, In this case, it is necessary to set your own expected timeout through time.After.

4. Note that you need to call cancel(), otherwise you’ll have to wait 800 milliseconds before the context is released.

conclusion

The timeout control above uses ctx.Done and time.after together.

The Done channel is responsible for listening for when the context is finished. If you haven’t finished the context before the timeout set in time.After, then I won’t wait and execute the logic After the timeout.

The lines

So, what else is there to do with time-out control?

Yes, but it’s much the same.

First: use time.NewTimer

func AsyncCall(a) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
	defer cancel()
	timer := time.NewTimer(time.Duration(time.Millisecond * 900))

	go func(ctx context.Context) {
		// Send an HTTP request} ()select {
	case <-ctx.Done():
		timer.Stop()
		timer.Reset(time.Second)
		fmt.Println("call successfully!!!")
		return
	case <-timer.C:
		fmt.Println("timeout!!!")
		return}}Copy the code

The main difference here is that time.After is replaced by time.NewTimer. The same idea is that if the interface call completes early, the Done signal is listened for and the timer is turned off.

Otherwise, the time-out business logic will be executed after the specified timer is 900 milliseconds.

Second: use channels

func AsyncCall(a) {
  ctx := context.Background()
	done := make(chan struct{}, 1)

	go func(ctx context.Context) {
		// Send an HTTP request
		done <- struct{} {}} ()select {
	case <-done:
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(800 * time.Millisecond)):
		fmt.Println("timeout!!!")
		return}}Copy the code

1. The feature that channels can communicate between coroutines is mainly used here. When the call is successful, signals are sent to the DONE channel.

2, listen for the Done signal, if received before time.After timeout, return normal, otherwise go to the time.After timeout logic, execute the timeout logic code.

3.A combination of channels and time.After is used, or a combination of channels and time.NewTimer can be used.

conclusion

This article mainly introduces how to implement timeout control, there are three main kinds

1, the context. WithTimeout/context. WithDeadline + time. After

2, the context. WithTimeout/context. WithDeadline + time. NewTimer

Channel + time.After/time.NewTimer

Welcome to JackieZheng