159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
// Copyright (c) 2017 Uber Technologies, Inc.
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package tchannel
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type relayTimerTrigger func(items *relayItems, id uint32, isOriginator bool)
|
|
|
|
type relayTimerPool struct {
|
|
pool sync.Pool
|
|
trigger relayTimerTrigger
|
|
verify bool
|
|
}
|
|
|
|
type relayTimer struct {
|
|
pool *relayTimerPool // const
|
|
timer *time.Timer // const
|
|
|
|
active bool // mutated on Start/Stop
|
|
stopped bool // mutated on Stop
|
|
released bool // mutated on Get/Release.
|
|
|
|
// Per-timer parameters passed back when the timer is triggered.
|
|
items *relayItems
|
|
id uint32
|
|
isOriginator bool
|
|
}
|
|
|
|
func (rt *relayTimer) OnTimer() {
|
|
rt.verifyNotReleased()
|
|
items, id, isOriginator := rt.items, rt.id, rt.isOriginator
|
|
rt.markTimerInactive()
|
|
rt.pool.trigger(items, id, isOriginator)
|
|
}
|
|
|
|
func newRelayTimerPool(trigger relayTimerTrigger, verify bool) *relayTimerPool {
|
|
return &relayTimerPool{
|
|
trigger: trigger,
|
|
verify: verify,
|
|
}
|
|
}
|
|
|
|
// Get returns a relay timer that has not started. Timers must be started explicitly
|
|
// using the Start function.
|
|
func (tp *relayTimerPool) Get() *relayTimer {
|
|
timer, ok := tp.pool.Get().(*relayTimer)
|
|
if ok {
|
|
timer.released = false
|
|
return timer
|
|
}
|
|
|
|
rt := &relayTimer{
|
|
pool: tp,
|
|
}
|
|
// Go timers are started by default. However, we need to separate creating
|
|
// the timer and starting the timer for use in the relay code paths.
|
|
// To make this work without more locks in the relayTimer, we create a Go timer
|
|
// with a huge timeout so it doesn't run, then stop it so we can start it later.
|
|
rt.timer = time.AfterFunc(time.Duration(math.MaxInt64), rt.OnTimer)
|
|
if !rt.timer.Stop() {
|
|
panic("relayTimer requires timers in stopped state, but failed to stop underlying timer")
|
|
}
|
|
return rt
|
|
}
|
|
|
|
// Put returns a relayTimer back to the pool.
|
|
func (tp *relayTimerPool) Put(rt *relayTimer) {
|
|
if tp.verify {
|
|
// If we are trying to verify correct pool behavior, then we don't release
|
|
// the timer, and instead ensure no methods are called after being released.
|
|
return
|
|
}
|
|
tp.pool.Put(rt)
|
|
}
|
|
|
|
// Start starts a timer with the given duration for the specified ID.
|
|
func (rt *relayTimer) Start(d time.Duration, items *relayItems, id uint32, isOriginator bool) {
|
|
rt.verifyNotReleased()
|
|
if rt.active {
|
|
panic("Tried to start an already-active timer")
|
|
}
|
|
|
|
rt.active = true
|
|
rt.stopped = false
|
|
rt.items = items
|
|
rt.id = id
|
|
rt.isOriginator = isOriginator
|
|
|
|
if wasActive := rt.timer.Reset(d); wasActive {
|
|
panic("relayTimer's underlying timer was Started multiple times without Stop")
|
|
}
|
|
}
|
|
|
|
func (rt *relayTimer) markTimerInactive() {
|
|
rt.active = false
|
|
rt.items = nil
|
|
rt.id = 0
|
|
rt.items = nil
|
|
rt.isOriginator = false
|
|
}
|
|
|
|
// Stop stops the timer and returns whether the timer was stopped.
|
|
// If the timer has been executed, it returns false, but in all other
|
|
// cases, it returns true (even if the timer was stopped previously).
|
|
func (rt *relayTimer) Stop() bool {
|
|
rt.verifyNotReleased()
|
|
|
|
if rt.stopped {
|
|
return true
|
|
}
|
|
|
|
stopped := rt.timer.Stop()
|
|
if stopped {
|
|
rt.stopped = true
|
|
rt.markTimerInactive()
|
|
}
|
|
|
|
return stopped
|
|
}
|
|
|
|
// Release releases a timer back to the timer pool. The timer MUST have run or be
|
|
// stopped before Release is called.
|
|
func (rt *relayTimer) Release() {
|
|
rt.verifyNotReleased()
|
|
if rt.active {
|
|
panic("only stopped or completed timers can be released")
|
|
}
|
|
rt.released = true
|
|
rt.pool.Put(rt)
|
|
}
|
|
|
|
func (rt *relayTimer) verifyNotReleased() {
|
|
if rt.released {
|
|
panic("Released timer cannot be used")
|
|
}
|
|
}
|