diff --git a/pkg/kv/dist_sender.go b/pkg/kv/dist_sender.go index 00b422f94b03..aa1e808ec8b5 100644 --- a/pkg/kv/dist_sender.go +++ b/pkg/kv/dist_sender.go @@ -1138,8 +1138,8 @@ func (ds *DistSender) sendToReplicas( // Wait for completions. This loop will retry operations that fail // with errors that reflect per-replica state and may succeed on // other replicas. - var sendNextTimer timeutil.Timer - var slowTimer timeutil.Timer + sendNextTimer := timeutil.NewTimer() + slowTimer := timeutil.NewTimer() defer sendNextTimer.Stop() defer slowTimer.Stop() slowTimer.Reset(base.SlowRequestThreshold) diff --git a/pkg/storage/replica.go b/pkg/storage/replica.go index 073b701cf108..21c1278eb8c0 100644 --- a/pkg/storage/replica.go +++ b/pkg/storage/replica.go @@ -1045,7 +1045,7 @@ func (r *Replica) redirectOnOrAcquireLease(ctx context.Context) (LeaseStatus, *r // Wait for the range lease to finish, or the context to expire. pErr = func() *roachpb.Error { - var slowTimer timeutil.Timer + slowTimer := timeutil.NewTimer() defer slowTimer.Stop() slowTimer.Reset(base.SlowRequestThreshold) for { @@ -1676,7 +1676,7 @@ func (r *Replica) beginCmds(ctx context.Context, ba *roachpb.BatchRequest) (*end // However, the command queue assumes that commands don't drop // out before their prerequisites, so we still have to wait it // out. - var slowTimer timeutil.Timer + slowTimer := timeutil.NewTimer() defer slowTimer.Stop() slowTimer.Reset(base.SlowRequestThreshold) for _, ch := range chans { @@ -2158,7 +2158,7 @@ func (r *Replica) tryAddWriteCmd( // If the command was accepted by raft, wait for the range to apply it. ctxDone := ctx.Done() shouldQuiesce := r.store.stopper.ShouldQuiesce() - var slowTimer timeutil.Timer + slowTimer := timeutil.NewTimer() defer slowTimer.Stop() slowTimer.Reset(base.SlowRequestThreshold) for { diff --git a/pkg/util/timeutil/timer.go b/pkg/util/timeutil/timer.go index 7a2e2eb2c0e0..7a1f0a639069 100644 --- a/pkg/util/timeutil/timer.go +++ b/pkg/util/timeutil/timer.go @@ -16,7 +16,17 @@ package timeutil -import "time" +import ( + "sync" + "time" +) + +var timerPool = sync.Pool{ + New: func() interface{} { + return &Timer{} + }, +} +var timeTimerPool sync.Pool // The Timer type represents a single event. When the Timer expires, // the current time will be sent on Timer.C. @@ -53,15 +63,25 @@ type Timer struct { Read bool } +// NewTimer allocates a new timer. +func NewTimer() *Timer { + return timerPool.Get().(*Timer) +} + // Reset changes the timer to expire after duration d and returns // the new value of the timer. This method includes the fix proposed // in https://github.com/golang/go/issues/11513#issuecomment-157062583, // but requires users of Timer to set Timer.Read to true whenever -// they successfully read from the Timer's channel. Reset operates on -// and returns a value so that Timer can be stack allocated. +// they successfully read from the Timer's channel. func (t *Timer) Reset(d time.Duration) { if t.timer == nil { - t.timer = time.NewTimer(d) + switch timer := timeTimerPool.Get(); timer { + case nil: + t.timer = time.NewTimer(d) + default: + t.timer = timer.(*time.Timer) + t.timer.Reset(d) + } t.C = t.timer.C return } @@ -77,8 +97,16 @@ func (t *Timer) Reset(d time.Duration) { // or had never been initialized with a call to Timer.Reset. Stop does not // close the channel, to prevent a read from succeeding incorrectly. func (t *Timer) Stop() bool { - if t.timer == nil { - return false + var res bool + if t.timer != nil { + res = t.timer.Stop() + if res { + // Only place the timer back in the pool if we successfully stopped + // it. Otherwise, we'd have to read from the channel if !t.Read. + timeTimerPool.Put(t.timer) + } + t.timer = nil } - return t.timer.Stop() + timerPool.Put(t) + return res }