Details of timer traps in Golang

  • 2020-06-19 10:31:58
  • OfStack

preface

In business, we often need to implement functions based on timed tasks. Examples include TTL session management, locking, timing tasks (alarm clocks), or more complex state switches. The Main thing about the Golang timer trap is that it's not what you think it is, and that cognitive error can leave your software behind to hide Bug. So Timer happens to have three traps, which we'll talk about

1) The trap and Reset

2) Trap of the channel,

3) Stop's trap is similar to Reset's trap, so explore for yourself.

Without further ado, let's take a look at the details

Where is the Reset trap

Timer.Reset() The return value of the function is of type bool. Let's take a look at the following question:

What does its return value represent? What do we want to be successful at? What is failure?

Success: The timer timed out after 1 period and a timeout event was received.

Failure: The opposite of success, we don't receive that event. For failure, we should do something to make sure our timer works.

Is that what the return value of Reset means?

By looking at the documentation and implementation, Timer.Reset() The return value of is not what we expected. That's the error. Its return value does not represent a reset timer success or failure, but rather represents the state of the timer before the reset:

When Timer has stopped or timed out, return false. true is returned when the timer does not time out.

So, when Reset returns false, we can't assume that after 1 period of time, the timeout won't come. In fact, it might come, and the timer is in effect.

Skip the trap and meet the trap again

How can we skip the previous pitfalls and get Reset to work as expected? Ignore the return value of Reset, it won't help you achieve the desired effect.

The real trap is the Timer channel, which is closely related to our expected successes and failures. The expected timer setting failure is usually only related to the channel: whether there is already data in the timer channel ES54en.C before the timer is set.

If there is, the timer we set failed, and we might read an incorrect timeout event. If not, the timer we set succeeds and we get the timeout event at the set time.

Next, explain why the failure is only related to the presence or absence of timeout events in the channel.

Timer cache channel size is only 1, can not store more timeout events, see the source.


// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
func NewTimer(d Duration) *Timer {
 c := make(chan Time, 1) //  The cache channel size is 1
 t := &Timer{
  C: c,
  r: runtimeTimer{
   when: when(d),
   f: sendTime,
   arg: c,
  },
 }
 startTimer(&t.r)
 return t
}

After the timer is created, it runs separately. After timeout, it writes data to the channel, and you read the data from the channel. The current timeout data is not read, but a new timer is set, and then I go to the channel to read the data. What I get is the timeout event of the last timeout, which looks like a success but actually fails, completely falling into the trap.

Cross the trap and ensure success

If you make sure Timer.Reset() Success, getting the results we want? Timer.Reset() Clear the front passage.

When the business scenario is simple, there is no need to actively clear the channels. For example, the processing flow is as follows: set the timer once, process the timer once, without interruption, and the channel must be empty before the next Reset.

When the business scenario is complex and you are not sure if the channel is empty, actively clear it.


if len(Timer.C) > 0{
 <-Timer.C
}
Timer.Reset(time.Second)

The test code


package main

import (
 "fmt"
 "time"
)

//  In different cases, Timer.Reset() The return value of the 
func test1() {
 fmt.Println(" The first 1 A test: Reset What does the return value relate to? ")
 tm := time.NewTimer(time.Second)
 defer tm.Stop()

 quit := make(chan bool)

 //  Exit event 
 go func() {
  time.Sleep(3 * time.Second)
  quit <- true
 }()

 // Timer No timeout. Look Reset The return value of the 
 if !tm.Reset(time.Second) {
  fmt.Println(" Not a timeout, Reset return false")
 } else {
  fmt.Println(" Not a timeout, Reset return true")
 }

 //  stop timer
 tm.Stop()
 if !tm.Reset(time.Second) {
  fmt.Println(" stop Timer . Reset return false")
 } else {
  fmt.Println(" stop Timer . Reset return true")
 }

 // Timer timeout 
 for {
  select {
  case <-quit:
   return

  case <-tm.C:
   if !tm.Reset(time.Second) {
    fmt.Println(" Timeout, Reset return false")
   } else {
    fmt.Println(" Timeout, Reset return true")
   }
  }
 }
}

func test2() {
 fmt.Println("\n The first 2 A test : After timeout, do not read the events in the channel, ok Reset Successful? ")
 sm2Start := time.Now()
 tm2 := time.NewTimer(time.Second)
 time.Sleep(2 * time.Second)
 fmt.Printf("Reset The number of events in the front channel :%d\n", len(tm2.C))
 if !tm2.Reset(time.Second) {
  fmt.Println(" Instead of reading channel data, Reset return false")
 } else {
  fmt.Println(" Instead of reading channel data, Reset return true")
 }
 fmt.Printf("Reset The number of events in the back channel :%d\n", len(tm2.C))

 select {
 case t := <-tm2.C:
  fmt.Printf("tm2 Start time : %v\n", sm2Start.Unix())
  fmt.Printf(" Time of events in channel: %v\n", t.Unix())
  if t.Sub(sm2Start) <= time.Second+time.Millisecond {
   fmt.Println(" The time in the channel is reset sm2 The preceding time, that is, the first 1 The second time out, so number one 2 time Reset failed ")
  }
 }

 fmt.Printf(" After reading the channel, the number of events in it :%d\n", len(tm2.C))
 tm2.Reset(time.Second)
 fmt.Printf(" Once again, Reset After, the number of events in the channel :%d\n", len(tm2.C))
 time.Sleep(2 * time.Second)
 fmt.Printf(" The number of events in the channel after the timeout :%d\n", len(tm2.C))
}

func test3() {
 fmt.Println("\n The first 3 A test: Reset Clear the front passage as unobtrusive as possible ")
 smStart := time.Now()
 tm := time.NewTimer(time.Second)
 time.Sleep(2 * time.Second)
 if len(tm.C) > 0 {
  <-tm.C
 }
 tm.Reset(time.Second)

 //  timeout 
 t := <-tm.C
 fmt.Printf("tm Start time : %v\n", smStart.Unix())
 fmt.Printf(" Time of events in channel: %v\n", t.Unix())
 if t.Sub(smStart) <= time.Second+time.Millisecond {
  fmt.Println(" The time in the channel is reset sm The preceding time, that is, the first 1 The second time out, so number one 2 time Reset failed ")
 } else {
  fmt.Println(" The time in the channel is reset sm In the time after, Reset A success ")
 }
}

func main() {
 test1()
 test2()
 test3()
}

conclusion


Related articles: