github.com/haraldrudell/parl@v0.4.176/ptime/thread-safe-timer.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package ptime
     7  
     8  import (
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  const (
    14  	// if new-function is invoked with zero default duration,
    15  	// default default-duration is 1 second
    16  	defaultDefaultDuration = time.Second
    17  )
    18  
    19  // ThreadSafeTimer is a timer that can be Reset by multiple threads. Improvements:
    20  //   - Stop and Reset are thread-safe
    21  //   - Reset includes Stop in a fail-safe sequence and can be invoked at any time by any thread
    22  //   - NewThreadSafeTimer has a defaultDuration of 1 second if argument is missing, zero or negative
    23  //   - Reset also uses defaultDuration if argument is zero or negative
    24  //   - fields and method signatures are identical to [time.Timer]
    25  //   - ThreadSafeTimer may be copied or stored in slices or maps
    26  //   - —
    27  //   - cost: 1 sync.Mutex, 1 int64, 2 pointers, 2 allocations
    28  //   - [time.Timer] public methods and fields: C Reset Stop
    29  //   - time.Timer Reset must be in an uninterrupted Stop-drain-Reset sequence or memory leaks result
    30  type ThreadSafeTimer struct {
    31  	// the default duration for Reset method
    32  	defaultDuration time.Duration
    33  	// the timer initially stopped
    34  	*time.Timer
    35  	// resetLock makes Stop, drain and Reset sequence atomic
    36  	resetLock *sync.Mutex
    37  }
    38  
    39  // NewThreadSafeTimer returns a running timer with thread-safe Reset
    40  //   - default defaultDuration is 1 second for defaultDuration missing, zero or negative
    41  //   - defaultDuration is the default for Reset when Reset argument is zero or negative
    42  //   - Reset can be invoked at any time without any precautions.
    43  //   - — [time.Timer.Reset] has many conditions to avoid memory leaks
    44  //   - Stop and Reset methods are thread-safe
    45  //   - a timer must either expire or have Stop invoked to release resources
    46  //   - if timer was created in same thread or obtained via synchronize before,
    47  //     read of field C is thread-safe
    48  func NewThreadSafeTimer(defaultDuration ...time.Duration) (timer *ThreadSafeTimer) {
    49  
    50  	// determine default duration
    51  	var d time.Duration
    52  	if len(defaultDuration) > 0 {
    53  		d = defaultDuration[0]
    54  	}
    55  	if d <= 0 {
    56  		d = defaultDefaultDuration // 1 second
    57  	}
    58  
    59  	return &ThreadSafeTimer{
    60  		defaultDuration: d,
    61  		Timer:           time.NewTimer(d),
    62  		resetLock:       &sync.Mutex{},
    63  	}
    64  }
    65  
    66  // Reset is thread-safe timer reset
    67  //   - has default duration for duration == 0
    68  //   - works with concurrent channel read
    69  //   - works with concurrent timer.Stop
    70  //   - —
    71  //   - thread-safety is obtained by making the Stop-drain-Reset sequence atomic
    72  //   - unsynchronized Reset will cause memory leaks
    73  func (t *ThreadSafeTimer) Reset(duration time.Duration) {
    74  	if duration <= 0 {
    75  		duration = t.defaultDuration
    76  	}
    77  	t.resetLock.Lock()
    78  	defer t.resetLock.Unlock()
    79  
    80  	// Reset should be invoked only on:
    81  	//	- stopped or expired timers
    82  	//	- with drained channels
    83  	t.Timer.Stop()
    84  	select {
    85  	case <-t.Timer.C:
    86  	default:
    87  	}
    88  	t.Timer.Reset(duration)
    89  }
    90  
    91  // Stop prevents the Timer from firing
    92  func (t *ThreadSafeTimer) Stop() (wasRunning bool) {
    93  	t.resetLock.Lock()
    94  	defer t.resetLock.Unlock()
    95  
    96  	return t.Timer.Stop()
    97  }