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

     1  /*
     2  
     3  Copyright (c) 2024 - 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  func NewInterval(action ContextAction, interval time.Duration, options ...IntervalOption) *Interval {
    27  	i := Interval{
    28  		Latch:    NewLatch(),
    29  		Action:   action,
    30  		Context:  context.Background(),
    31  		Interval: interval,
    32  	}
    33  	for _, option := range options {
    34  		option(&i)
    35  	}
    36  	return &i
    37  }
    38  
    39  // IntervalOption is an option for the interval worker.
    40  type IntervalOption func(*Interval)
    41  
    42  // OptIntervalDelay sets the interval worker start delay.
    43  func OptIntervalDelay(d time.Duration) IntervalOption {
    44  	return func(i *Interval) {
    45  		i.Delay = d
    46  	}
    47  }
    48  
    49  // OptIntervalContext sets the interval worker context.
    50  func OptIntervalContext(ctx context.Context) IntervalOption {
    51  	return func(i *Interval) {
    52  		i.Context = ctx
    53  	}
    54  }
    55  
    56  // OptIntervalStopOnError sets if the interval worker should stop on action error.
    57  func OptIntervalStopOnError(stopOnError bool) IntervalOption {
    58  	return func(i *Interval) {
    59  		i.StopOnError = stopOnError
    60  	}
    61  }
    62  
    63  // OptIntervalErrors sets the interval worker start error channel.
    64  func OptIntervalErrors(errors chan error) IntervalOption {
    65  	return func(i *Interval) {
    66  		i.Errors = errors
    67  	}
    68  }
    69  
    70  // Interval is a background worker that performs an action on an interval.
    71  type Interval struct {
    72  	*Latch
    73  	Context     context.Context
    74  	Interval    time.Duration
    75  	Action      ContextAction
    76  	Delay       time.Duration
    77  	StopOnError bool
    78  	Errors      chan error
    79  }
    80  
    81  /*
    82  Start starts the worker.
    83  
    84  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.
    85  
    86  This call will block.
    87  */
    88  func (i *Interval) Start() error {
    89  	if !i.CanStart() {
    90  		return ex.New(ErrCannotStart)
    91  	}
    92  	i.Starting()
    93  	return i.Dispatch()
    94  }
    95  
    96  // Stop stops the worker.
    97  func (i *Interval) Stop() error {
    98  	if !i.CanStop() {
    99  		return ex.New(ErrCannotStop)
   100  	}
   101  	i.Stopping()
   102  	<-i.NotifyStopped()
   103  	i.Latch.Reset() // reset the latch in case we have to start again
   104  	return nil
   105  }
   106  
   107  // Dispatch is the main dispatch loop.
   108  func (i *Interval) Dispatch() (err error) {
   109  	i.Started()
   110  
   111  	if i.Delay > 0 {
   112  		time.Sleep(i.Delay)
   113  	}
   114  
   115  	tick := time.NewTicker(i.Interval)
   116  	defer func() {
   117  		tick.Stop()
   118  		i.Stopped()
   119  	}()
   120  
   121  	var stopping <-chan struct{}
   122  	for {
   123  		stopping = i.NotifyStopping()
   124  		// check stopping conditions first
   125  		select {
   126  		case <-i.Context.Done():
   127  			return
   128  		case <-stopping:
   129  			return
   130  		default:
   131  		}
   132  
   133  		select {
   134  		case <-tick.C:
   135  			err = i.Action(context.Background())
   136  			if err != nil {
   137  				if i.StopOnError {
   138  					return
   139  				}
   140  				if i.Errors != nil {
   141  					i.Errors <- err
   142  				}
   143  			}
   144  		case <-i.Context.Done():
   145  			return
   146  		case <-stopping:
   147  			return
   148  		}
   149  	}
   150  }