github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/timer.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package timeutil
    12  
    13  import (
    14  	"sync"
    15  	"time"
    16  )
    17  
    18  var timerPool = sync.Pool{
    19  	New: func() interface{} {
    20  		return &Timer{}
    21  	},
    22  }
    23  var timeTimerPool sync.Pool
    24  
    25  // The Timer type represents a single event. When the Timer expires,
    26  // the current time will be sent on Timer.C.
    27  //
    28  // This timer implementation is an abstraction around the standard
    29  // library's time.Timer that provides a temporary workaround for the
    30  // issue described in https://github.com/golang/go/issues/14038. As
    31  // such, this timer should only be used when Reset is planned to
    32  // be called continually in a loop. For this Reset pattern to work,
    33  // Timer.Read must be set to true whenever a timestamp is read from
    34  // the Timer.C channel. If Timer.Read is not set to true when the
    35  // channel is read from, the next call to Timer.Reset will deadlock.
    36  // This pattern looks something like:
    37  //
    38  //	var timer timeutil.Timer
    39  //	defer timer.Stop()
    40  //	for {
    41  //	    timer.Reset(wait)
    42  //	    select {
    43  //	    case <-timer.C:
    44  //	        timer.Read = true
    45  //	        ...
    46  //	    }
    47  //	}
    48  //
    49  // Note that unlike the standard library's Timer type, this Timer will
    50  // not begin counting down until Reset is called for the first time, as
    51  // there is no constructor function.
    52  type Timer struct {
    53  	timer *time.Timer
    54  	// C is a local "copy" of timer.C that can be used in a select case before
    55  	// the timer has been initialized (via Reset).
    56  	C    <-chan time.Time
    57  	Read bool
    58  }
    59  
    60  // NewTimer allocates a new timer.
    61  func NewTimer() *Timer {
    62  	return timerPool.Get().(*Timer)
    63  }
    64  
    65  // Reset changes the timer to expire after duration d and returns
    66  // the new value of the timer. This method includes the fix proposed
    67  // in https://github.com/golang/go/issues/11513#issuecomment-157062583,
    68  // but requires users of Timer to set Timer.Read to true whenever
    69  // they successfully read from the Timer's channel.
    70  func (t *Timer) Reset(d time.Duration) {
    71  	if t.timer == nil {
    72  		switch timer := timeTimerPool.Get(); timer {
    73  		case nil:
    74  			t.timer = time.NewTimer(d)
    75  		default:
    76  			t.timer = timer.(*time.Timer)
    77  			t.timer.Reset(d)
    78  		}
    79  		t.C = t.timer.C
    80  		return
    81  	}
    82  	if !t.timer.Stop() && !t.Read {
    83  		<-t.C
    84  	}
    85  	t.timer.Reset(d)
    86  	t.Read = false
    87  }
    88  
    89  // Stop prevents the Timer from firing. It returns true if the call stops
    90  // the timer, false if the timer has already expired, been stopped previously,
    91  // or had never been initialized with a call to Timer.Reset. Stop does not
    92  // close the channel, to prevent a read from succeeding incorrectly.
    93  // Note that a Timer must never be used again after calls to Stop as the timer
    94  // object will be put into an object pool for reuse.
    95  func (t *Timer) Stop() bool {
    96  	var res bool
    97  	if t.timer != nil {
    98  		res = t.timer.Stop()
    99  		if res {
   100  			// Only place the timer back in the pool if we successfully stopped
   101  			// it. Otherwise, we'd have to read from the channel if !t.Read.
   102  			timeTimerPool.Put(t.timer)
   103  		}
   104  	}
   105  	*t = Timer{}
   106  	timerPool.Put(t)
   107  	return res
   108  }