Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task with circle property maybe trigger multitimes in one ticker #32

Open
simonwu-os opened this issue Mar 12, 2023 · 0 comments
Open

Comments

@simonwu-os
Copy link

simonwu-os commented Mar 12, 2023

package main
import (
  "fmt"
  "sync"
  "time"
  "github.com/rfyiamcool/go-timewheel"
)
func main() {
    TW1s, _ := timewheel.NewTimeWheel(1*time.Second, 2)
    TW1s.Start()
    defer TW1s.Stop()
    now := time.Now()
    var zsync sync.Mutex
    TW1s.AfterFunc(1*time.Second, func() {
      fmt.Println("cost(tick:1s+after:1s):", time.Since(now))
    })
    last := time.Now()
    my := &last
    wait := make(chan int)
    task := TW1s.AddCron(2*time.Second, func() {
      zsync.Lock()
      defer zsync.Unlock()
      if time.Since(*my) < time.Second/2 {
        fmt.Print("=============== retry ====>")
        wait <- 1
      }
      fmt.Println("cost(tick:2s+after:1s):", time.Since(now), time.Since(*my))
      zd := time.Now()
      TW1s.AfterFunc(2*time.Second, func() {
        fmt.Println("cost(tick:2s):", time.Since(zd))
      })
      *my = time.Now()
    })

    fmt.Printf("%#v\n", task)
    <-wait
    TW1s.Sleep(3 * time.Second)
  }

If the task is triggered twice in one ticker, then it prints "=============== retry ====>" and exits the program later.
You will see output similars to the following output:
=============== retry ====>cost(tick:2s+after:1s): 5.000353629s 5.424µs

We can run it in https://go.dev/tour/welcome/1.
We maybe see the output

cost(tick:2s+after:1s): 13s 2s
cost(tick:2s): 3s
cost(tick:2s+after:1s): 15s 2s
=============== retry ====>cost(tick:2s+after:1s): 15s 0s
cost(tick:2s): 3s

or

cost(tick:1s+after:1s): 2s
cost(tick:2s+after:1s): 3s 3s
=============== retry ====>cost(tick:2s+after:1s): 3s 0s
cost(tick:2s+after:1s): 5s 2s
cost(tick:2s): 3s

The cause for it:

In func (tw *TimeWheel) handleTick(),

  bucket := tw.buckets[tw.currentIndex]

  for k, task := range bucket {
      ...
      if task.circle == true {
        tw.collectTask(task)      
        tw.putCircle(task, modeIsCircle)
      }
      continue
    }
    ...
  }

If in tw.putCircle the insert index equals tw.currentIndex, then we insert a task in map when iterates the map.
Because golang iterates map starting at any position randomly, then we maybe iterate the new inserted value in the iteration loop.
It maybe trigger more times than once.

How to resolve it?

  bucket := tw.buckets[tw.currentIndex]

  ///add slice
  var cycle_task []*Task

  for k, task := range bucket {
      ...
    if task.circle == true {
      tw.collectTask(task)

      if tw.calculateIndex(task.delay) == tw.currentIndex {
        cycle_task = append(cycle_task, task)
      } else {
        tw.putCircle(task, modeIsCircle)
      }
      continue
   }
   ...
 }

 /// add tasks in the slice
 for _, task := range cycle_task {
    tw.putCircle(task, modeIsCircle)
 }

Then you can run the previous main function will not exit for very long time until you kill it forcefully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant