github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/state/coalescer.go (about) 1 package state 2 3 import ( 4 "context" 5 "time" 6 ) 7 8 // Coalescer performs coalesced signaling, combining multiple signals that occur 9 // within a specified time window. A Coalescer is safe for concurrent usage. It 10 // maintains a background Goroutine that must be terminated using Terminate. 11 type Coalescer struct { 12 // strobes is used to transmit strobes to the run loop. 13 strobes chan struct{} 14 // signals is the channel on which signals are delivered. 15 signals chan struct{} 16 // cancel signals termination to the run loop. 17 cancel context.CancelFunc 18 // done is closed to indicate that the run loop has exited. 19 done chan struct{} 20 } 21 22 // NewCoalescer creates a new coalescer that will group signals that occur 23 // within the specified time window of each other. If window is negative, it 24 // will be treated as zero. 25 func NewCoalescer(window time.Duration) *Coalescer { 26 // If the specified window is negative, then treat it as zero. 27 if window < 0 { 28 window = 0 29 } 30 31 // Create a cancellable context to regulate the run loop. 32 ctx, cancel := context.WithCancel(context.Background()) 33 34 // Create the coalescer. 35 coalescer := &Coalescer{ 36 strobes: make(chan struct{}), 37 signals: make(chan struct{}, 1), 38 cancel: cancel, 39 done: make(chan struct{}), 40 } 41 42 // Start the coalescer's run loop. 43 go coalescer.run(ctx, window) 44 45 // Done. 46 return coalescer 47 } 48 49 // run implements the signal processing run loop for Coalescer. 50 func (c *Coalescer) run(ctx context.Context, window time.Duration) { 51 // Create the (initially stopped) coalescing timer. 52 timer := time.NewTimer(0) 53 if !timer.Stop() { 54 <-timer.C 55 } 56 57 // Loop and process events until cancelled. 58 for { 59 select { 60 case <-ctx.Done(): 61 timer.Stop() 62 close(c.done) 63 return 64 case <-c.strobes: 65 timer.Stop() 66 select { 67 case <-timer.C: 68 default: 69 } 70 timer.Reset(window) 71 case <-timer.C: 72 select { 73 case c.signals <- struct{}{}: 74 default: 75 } 76 } 77 } 78 } 79 80 // Strobe enqueues a signal to be sent after the coalescing window. If a 81 // subsequent call to Strobe is made within the coalescing window, then it will 82 // reset the coalescing timer and an event will only be sent after Strobe hasn't 83 // been called for the coalescing window period. 84 func (c *Coalescer) Strobe() { 85 select { 86 case c.strobes <- struct{}{}: 87 case <-c.done: 88 } 89 } 90 91 // Signals returns the signal notification channel. This channel is buffered 92 // with a capacity of 1, so no signaling will ever be lost if it's not actively 93 // polled. The resulting channel is never closed. 94 func (c *Coalescer) Signals() <-chan struct{} { 95 return c.signals 96 } 97 98 // Terminate shuts down the coalescer's internal run loop and waits for it to 99 // terminate. It's safe to continue invoking other methods after invoking 100 // Terminate (including Terminate, which is idempotent), though Strobe will have 101 // no effect and only previously buffered events will be delivered on the 102 // channel returned by Signals. 103 func (c *Coalescer) Terminate() { 104 c.cancel() 105 <-c.done 106 }