github.com/blend/go-sdk@v1.20220411.3/async/interval.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package async
     9  
    10  import (
    11  	"context"
    12  	"time"
    13  
    14  	"github.com/blend/go-sdk/ex"
    15  )
    16  
    17  /*
    18  NewInterval returns a new worker that runs an action on an interval.
    19  
    20  Example:
    21  
    22  	iw := NewInterval(func(ctx context.Context) error { return nil }, 500*time.Millisecond)
    23  	go iw.Start()
    24  	<-iw.Started()
    25  
    26  
    27  */
    28  func NewInterval(action ContextAction, interval time.Duration, options ...IntervalOption) *Interval {
    29  	i := Interval{
    30  		Latch:    NewLatch(),
    31  		Action:   action,
    32  		Context:  context.Background(),
    33  		Interval: interval,
    34  	}
    35  	for _, option := range options {
    36  		option(&i)
    37  	}
    38  	return &i
    39  }
    40  
    41  // IntervalOption is an option for the interval worker.
    42  type IntervalOption func(*Interval)
    43  
    44  // OptIntervalDelay sets the interval worker start delay.
    45  func OptIntervalDelay(d time.Duration) IntervalOption {
    46  	return func(i *Interval) {
    47  		i.Delay = d
    48  	}
    49  }
    50  
    51  // OptIntervalContext sets the interval worker context.
    52  func OptIntervalContext(ctx context.Context) IntervalOption {
    53  	return func(i *Interval) {
    54  		i.Context = ctx
    55  	}
    56  }
    57  
    58  // OptIntervalStopOnError sets if the interval worker should stop on action error.
    59  func OptIntervalStopOnError(stopOnError bool) IntervalOption {
    60  	return func(i *Interval) {
    61  		i.StopOnError = stopOnError
    62  	}
    63  }
    64  
    65  // OptIntervalErrors sets the interval worker start error channel.
    66  func OptIntervalErrors(errors chan error) IntervalOption {
    67  	return func(i *Interval) {
    68  		i.Errors = errors
    69  	}
    70  }
    71  
    72  // Interval is a background worker that performs an action on an interval.
    73  type Interval struct {
    74  	*Latch
    75  	Context     context.Context
    76  	Interval    time.Duration
    77  	Action      ContextAction
    78  	Delay       time.Duration
    79  	StopOnError bool
    80  	Errors      chan error
    81  }
    82  
    83  /*
    84  Start starts the worker.
    85  
    86  This will start the internal ticker, with a default initial delay of the given interval, and will return an ErrCannotStart if the interval worker is already started.
    87  
    88  This call will block.
    89  */
    90  func (i *Interval) Start() error {
    91  	if !i.CanStart() {
    92  		return ex.New(ErrCannotStart)
    93  	}
    94  	i.Starting()
    95  	return i.Dispatch()
    96  }
    97  
    98  // Stop stops the worker.
    99  func (i *Interval) Stop() error {
   100  	if !i.CanStop() {
   101  		return ex.New(ErrCannotStop)
   102  	}
   103  	i.Stopping()
   104  	<-i.NotifyStopped()
   105  	i.Latch.Reset() // reset the latch in case we have to start again
   106  	return nil
   107  }
   108  
   109  // Dispatch is the main dispatch loop.
   110  func (i *Interval) Dispatch() (err error) {
   111  	i.Started()
   112  
   113  	if i.Delay > 0 {
   114  		time.Sleep(i.Delay)
   115  	}
   116  
   117  	tick := time.NewTicker(i.Interval)
   118  	defer func() {
   119  		tick.Stop()
   120  		i.Stopped()
   121  	}()
   122  
   123  	var stopping <-chan struct{}
   124  	for {
   125  		stopping = i.NotifyStopping()
   126  		// check stopping conditions first
   127  		select {
   128  		case <-i.Context.Done():
   129  			return
   130  		case <-stopping:
   131  			return
   132  		default:
   133  		}
   134  
   135  		select {
   136  		case <-tick.C:
   137  			err = i.Action(context.Background())
   138  			if err != nil {
   139  				if i.StopOnError {
   140  					return
   141  				}
   142  				if i.Errors != nil {
   143  					i.Errors <- err
   144  				}
   145  			}
   146  		case <-i.Context.Done():
   147  			return
   148  		case <-stopping:
   149  			return
   150  		}
   151  	}
   152  }