github.com/xmidt-org/webpa-common@v1.11.9/capacitor/capacitor.go (about)

     1  package capacitor
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"time"
     7  
     8  	"github.com/xmidt-org/webpa-common/clock"
     9  )
    10  
    11  // DefaultDelay is the default time a capacitor waits to execute the most recently
    12  // submitted function
    13  const DefaultDelay time.Duration = time.Second
    14  
    15  // Interface represents a capacitor of function calls which will discharge after
    16  // a configurable period of time.
    17  type Interface interface {
    18  	// Submit submits a function for execution.  The function will not be executed immediately.
    19  	// Instead, after a configurable period of time, the most recent function passed to Submit will
    20  	// be executed.  The previous ones are ignored.
    21  	Submit(func())
    22  
    23  	// Discharge forcibly discharges this capacitor.  The most recent function passed to Submit is
    24  	// executed, and the internal state is reset so that the next call to Submit will start the
    25  	// process of delaying function calls all over again.
    26  	Discharge()
    27  
    28  	// Cancel terminates any waiting function call without executing it.  As with Discharge, the
    29  	// internal state is reset so that Submit calls will delay functions as normal again.
    30  	Cancel()
    31  }
    32  
    33  // Option represents a configuration option for a capacitor
    34  type Option func(*capacitor)
    35  
    36  func WithDelay(d time.Duration) Option {
    37  	return func(c *capacitor) {
    38  		if d > 0 {
    39  			c.delay = d
    40  		} else {
    41  			c.delay = DefaultDelay
    42  		}
    43  	}
    44  }
    45  
    46  func WithClock(cl clock.Interface) Option {
    47  	return func(c *capacitor) {
    48  		if cl != nil {
    49  			c.c = cl
    50  		} else {
    51  			c.c = clock.System()
    52  		}
    53  	}
    54  }
    55  
    56  // New creates a capacitor with the given options.
    57  func New(o ...Option) Interface {
    58  	c := &capacitor{
    59  		delay: DefaultDelay,
    60  		c:     clock.System(),
    61  	}
    62  
    63  	for _, f := range o {
    64  		f(c)
    65  	}
    66  
    67  	return c
    68  }
    69  
    70  // delayer is the internal job type that holds the context for a single, delayed
    71  // charge of the capacitor
    72  type delayer struct {
    73  	current   atomic.Value
    74  	timer     <-chan time.Time
    75  	terminate chan bool
    76  	cleanup   func()
    77  }
    78  
    79  func (d *delayer) discharge() {
    80  	d.terminate <- true
    81  }
    82  
    83  func (d *delayer) cancel() {
    84  	d.terminate <- false
    85  }
    86  
    87  func (d *delayer) execute() {
    88  	if f, ok := d.current.Load().(func()); f != nil && ok {
    89  		f()
    90  	}
    91  }
    92  
    93  // run is called as a goroutine and will exit when either the timer channel
    94  // is signalled or the terminate channel receives a value.
    95  func (d *delayer) run() {
    96  	defer d.cleanup()
    97  
    98  	select {
    99  	case <-d.timer:
   100  		// since the timer can fire at the same time as Discharge or Cancel,
   101  		// we want to make sure that any type of explicit termination trumps the timer
   102  		select {
   103  		case discharge := <-d.terminate:
   104  			if discharge {
   105  				d.execute()
   106  			}
   107  
   108  		default:
   109  			d.execute()
   110  		}
   111  
   112  	case discharge := <-d.terminate:
   113  		if discharge {
   114  			d.execute()
   115  		}
   116  	}
   117  }
   118  
   119  // capacitor implements Interface, and provides an atomically updated delayer job
   120  type capacitor struct {
   121  	lock  sync.Mutex
   122  	delay time.Duration
   123  	c     clock.Interface
   124  	d     *delayer
   125  }
   126  
   127  func (c *capacitor) Submit(v func()) {
   128  	c.lock.Lock()
   129  	if c.d == nil {
   130  		var (
   131  			t = c.c.NewTimer(c.delay)
   132  			d = &delayer{
   133  				terminate: make(chan bool, 1),
   134  				timer:     t.C(),
   135  			}
   136  		)
   137  
   138  		d.current.Store(v)
   139  
   140  		// create a cleanup closure that stops the timer and
   141  		// ensures that the given delayer is cleared, allowing
   142  		// for barging.
   143  		d.cleanup = func() {
   144  			t.Stop()
   145  			c.lock.Lock()
   146  			if c.d == d {
   147  				c.d = nil
   148  			}
   149  			c.lock.Unlock()
   150  		}
   151  
   152  		c.d = d
   153  		go c.d.run()
   154  	} else {
   155  		c.d.current.Store(v)
   156  	}
   157  
   158  	c.lock.Unlock()
   159  }
   160  
   161  func (c *capacitor) Discharge() {
   162  	c.lock.Lock()
   163  	if c.d != nil {
   164  		c.d.discharge()
   165  		c.d = nil
   166  	}
   167  
   168  	c.lock.Unlock()
   169  }
   170  
   171  func (c *capacitor) Cancel() {
   172  	c.lock.Lock()
   173  	if c.d != nil {
   174  		c.d.cancel()
   175  		c.d = nil
   176  	}
   177  
   178  	c.lock.Unlock()
   179  }