github.com/status-im/status-go@v1.1.0/transactions/conditionalrepeater.go (about) 1 package transactions 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 ) 8 9 // TaskFunc defines the task to be run. The context is canceled when Stop is 10 // called to early stop scheduled task. 11 type TaskFunc func(ctx context.Context) (done bool) 12 13 const ( 14 WorkNotDone = false 15 WorkDone = true 16 ) 17 18 // ConditionalRepeater runs a task at regular intervals until the task returns 19 // true. It doesn't allow running task in parallel and can be triggered early 20 // by call to RunUntilDone. 21 type ConditionalRepeater struct { 22 interval time.Duration 23 task TaskFunc 24 // nil if not running 25 ctx context.Context 26 ctxMu sync.Mutex 27 cancel context.CancelFunc 28 runNowCh chan bool 29 runNowMu sync.Mutex 30 } 31 32 func NewConditionalRepeater(interval time.Duration, task TaskFunc) *ConditionalRepeater { 33 return &ConditionalRepeater{ 34 interval: interval, 35 task: task, 36 runNowCh: make(chan bool, 1), 37 } 38 } 39 40 // RunUntilDone starts the task immediately and continues to run it at the defined 41 // interval until the task returns true. Can be called multiple times but it 42 // does not allow multiple concurrent executions of the task. 43 func (t *ConditionalRepeater) RunUntilDone() { 44 t.ctxMu.Lock() 45 defer func() { 46 t.runNowMu.Lock() 47 if len(t.runNowCh) == 0 { 48 t.runNowCh <- true 49 } 50 t.runNowMu.Unlock() 51 t.ctxMu.Unlock() 52 }() 53 54 if t.ctx != nil { 55 return 56 } 57 t.ctx, t.cancel = context.WithCancel(context.Background()) 58 59 go func() { 60 defer func() { 61 t.ctxMu.Lock() 62 defer t.ctxMu.Unlock() 63 t.cancel() 64 t.ctx = nil 65 }() 66 67 ticker := time.NewTicker(t.interval) 68 defer ticker.Stop() 69 70 for { 71 select { 72 // Stop was called or task returned true 73 case <-t.ctx.Done(): 74 return 75 // Scheduled execution 76 case <-ticker.C: 77 if t.task(t.ctx) { 78 return 79 } 80 // Start right away if requested 81 case <-t.runNowCh: 82 ticker.Reset(t.interval) 83 if t.task(t.ctx) { 84 t.runNowMu.Lock() 85 if len(t.runNowCh) == 0 { 86 t.runNowMu.Unlock() 87 return 88 } 89 t.runNowMu.Unlock() 90 } 91 } 92 } 93 }() 94 } 95 96 // Stop forcefully stops the running task by canceling its context. 97 func (t *ConditionalRepeater) Stop() { 98 t.ctxMu.Lock() 99 defer t.ctxMu.Unlock() 100 if t.ctx != nil { 101 t.cancel() 102 } 103 } 104 105 func (t *ConditionalRepeater) IsRunning() bool { 106 t.ctxMu.Lock() 107 defer t.ctxMu.Unlock() 108 return t.ctx != nil 109 }