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  }