utils/timeutil/timer.go (49 lines of code) (raw):
// Copyright (c) 2016-2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package timeutil
import (
"sync"
"time"
)
// Timer is a thread-safe adaptation of time.Timer intended for timeouts which
// may be periodically invalidated. A Timer can be started and cancelled multiple
// times before the Timer fires. Once a Timer fires, it cannot be used again.
type Timer struct {
// C will be closed once the Timer fires.
C chan struct{}
sync.Mutex
timer *time.Timer
cancel chan bool
duration time.Duration
}
// NewTimer creates a new Timer which is set to the given duration.
func NewTimer(d time.Duration) *Timer {
return &Timer{
C: make(chan struct{}),
cancel: make(chan bool),
duration: d,
}
}
// Start starts the Timer. Returns false if the timer has already started, or
// if the timer has already fired.
func (t *Timer) Start() bool {
t.Lock()
defer t.Unlock()
if t.timer != nil {
// Timer has already started.
return false
}
t.timer = time.NewTimer(t.duration)
// Must copy this reference since t.timer will be nil if Cancel is called.
c := t.timer.C
go func() {
select {
case <-c:
close(t.C)
case <-t.cancel:
}
}()
return true
}
// Cancel cancels the Timer. Returns false if the timer has not started, or
// if the timer has already fired.
func (t *Timer) Cancel() bool {
t.Lock()
defer t.Unlock()
if t.timer == nil {
// Timer has not started.
return false
}
if !t.timer.Stop() {
// Timer already fired.
return false
}
// Let the goroutine created by Start exit.
t.cancel <- true
t.timer = nil
return true
}