go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/periodic/handle.go (about)

     1  // Copyright (c) 2023 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  // Package periodic provides helpers for doing periodic work.
    22  package periodic
    23  
    24  import (
    25  	"context"
    26  	"sync"
    27  	"time"
    28  
    29  	"go.mway.dev/chrono/clock"
    30  )
    31  
    32  // A Func is a function that can be run periodically. A Func must abide by ctx.
    33  type Func func(ctx context.Context)
    34  
    35  // A Handle manages a [Func] that is running periodically.
    36  type Handle struct {
    37  	fn     Func
    38  	ctx    context.Context
    39  	cancel context.CancelFunc
    40  	clock  clock.Clock
    41  	wg     sync.WaitGroup
    42  }
    43  
    44  // Start applies the given options and starts running fn every period until
    45  // [Handle.Stop] is called. If the given period is <=0, fn will be executed
    46  // repeatedly without any delay.
    47  func Start(period time.Duration, fn Func, opts ...StartOption) *Handle {
    48  	return StartWithContext(context.Background(), period, fn, opts...)
    49  }
    50  
    51  // StartWithContext applies the given options and starts running fn every
    52  // period until ctx expires or [Handle.Stop] is called. If the given period is
    53  // <=0, fn will be executed repeatedly without any delay.
    54  func StartWithContext(
    55  	ctx context.Context,
    56  	period time.Duration,
    57  	fn Func,
    58  	opts ...StartOption,
    59  ) *Handle {
    60  	var (
    61  		options      = defaultStartOptions().With(opts...)
    62  		hctx, cancel = context.WithCancel(ctx)
    63  		h            = &Handle{
    64  			fn:     fn,
    65  			ctx:    hctx,
    66  			cancel: cancel,
    67  			clock:  options.Clock,
    68  		}
    69  		ready = make(chan struct{})
    70  	)
    71  
    72  	h.wg.Add(1)
    73  	go func() {
    74  		defer h.wg.Done()
    75  		h.run(period, ready)
    76  	}()
    77  
    78  	<-ready
    79  	return h
    80  }
    81  
    82  // Stop stops the [Func] being managed by h and waits for it to exit.
    83  func (h *Handle) Stop() {
    84  	h.cancel()
    85  	h.wg.Wait()
    86  }
    87  
    88  func (h *Handle) run(period time.Duration, ready chan<- struct{}) {
    89  	var tick <-chan time.Time
    90  	if period > 0 {
    91  		ticker := h.clock.NewTicker(period)
    92  		defer ticker.Stop()
    93  		tick = ticker.C
    94  	} else {
    95  		tmp := make(chan time.Time)
    96  		close(tmp)
    97  		tick = tmp
    98  	}
    99  
   100  	close(ready)
   101  
   102  	for {
   103  		select {
   104  		case <-h.ctx.Done():
   105  			return
   106  		case <-tick:
   107  			select {
   108  			case <-h.ctx.Done():
   109  				return
   110  			default:
   111  			}
   112  
   113  			h.fn(h.ctx)
   114  		}
   115  	}
   116  }