example_intervalrunner_test.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package atomic_test
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. "github.com/opencost/opencost/pkg/util/atomic"
  7. )
  8. // IntervalRunner is an example implementation of AtomicRunState.
  9. type IntervalRunner struct {
  10. runState atomic.AtomicRunState
  11. action func()
  12. interval time.Duration
  13. }
  14. // NewIntervalRunner Creates a new instance of an interval runner to execute the provided
  15. // function on a designated interval until explicitly stopped.
  16. func NewIntervalRunner(action func(), interval time.Duration) *IntervalRunner {
  17. return &IntervalRunner{
  18. action: action,
  19. interval: interval,
  20. }
  21. }
  22. // Start begins the interval execution. It returns true if the interval execution successfully starts.
  23. // It will return false if the interval execcution is already running.
  24. func (ir *IntervalRunner) Start() bool {
  25. // Before we attempt to start, we must ensure we are not in a stopping state, this is a common
  26. // pattern that should be used with the AtomicRunState
  27. ir.runState.WaitForReset()
  28. // This will atomically check the current state to ensure we can run, then advances the state.
  29. // If the state is already started, it will return false.
  30. if !ir.runState.Start() {
  31. return false
  32. }
  33. // our run state is advanced, let's execute our action on the interval
  34. // spawn a new goroutine which will loop and wait the interval each iteration
  35. go func() {
  36. for {
  37. // use a select statement to receive whichever channel receives data first
  38. select {
  39. // if our stop channel receives data, it means we have explicitly called
  40. // Stop(), and must reset our AtomicRunState to it's initial idle state
  41. case <-ir.runState.OnStop():
  42. ir.runState.Reset()
  43. return // exit go routine
  44. // After our interval elapses, fall through
  45. case <-time.After(ir.interval):
  46. }
  47. // Execute the function
  48. ir.action()
  49. // Loop back to the select where we will wait for the interval to elapse
  50. // or an explicit stop to be called
  51. }
  52. }()
  53. return true
  54. }
  55. // Stop will explicitly stop the execution of the interval runner. If an action is already executing, it will wait
  56. // until completion before processing the stop. Any attempts to start during the stopping phase will block until
  57. // it's possible to Start() again
  58. func (ir *IntervalRunner) Stop() bool {
  59. return ir.runState.Stop()
  60. }
  61. func Example_intervalRunner() {
  62. count := 0
  63. // As a general test, we'll use a goroutine which waits for a specific number of
  64. // ticks before calling stop, then issues a signal back to the main thread
  65. var wg sync.WaitGroup
  66. wg.Add(4)
  67. // Create a new IntervalRunner instance to execute our print action every second
  68. ir := NewIntervalRunner(
  69. func() {
  70. fmt.Printf("Tick[%d]\n", count)
  71. count++
  72. // advance the wait group count
  73. wg.Done()
  74. },
  75. time.Second,
  76. )
  77. // Start the runner, panic on failure
  78. if !ir.Start() {
  79. panic("Failed to start interval runner!")
  80. }
  81. // spin up a second goroutine which will wait for a specific number of
  82. // ticks before calling Stop(). This is a bit contrived, but demonstrates
  83. // multiple goroutines controlling the same interval runner.
  84. complete := make(chan bool)
  85. go func() {
  86. wg.Wait()
  87. // Stop the interval runner, notify main thread
  88. ir.Stop()
  89. complete <- true
  90. }()
  91. <-complete
  92. // Start immediately again using a different total tick count
  93. count = 0
  94. wg.Add(2)
  95. // Start the runner, panic on failure
  96. if !ir.Start() {
  97. panic("Failed to start interval runner!")
  98. }
  99. // Create a new Stop waiter
  100. go func() {
  101. wg.Wait()
  102. // Stop the interval runner, notify main thread
  103. ir.Stop()
  104. complete <- true
  105. }()
  106. <-complete
  107. // Output:
  108. // Tick[0]
  109. // Tick[1]
  110. // Tick[2]
  111. // Tick[3]
  112. // Tick[0]
  113. // Tick[1]
  114. }