// Copied from the go standard library. // // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE-BSD file. package multiplex import ( "sync" "time" ) // pipeDeadline is an abstraction for handling timeouts. type pipeDeadline struct { mu sync.Mutex // Guards timer and cancel timer *time.Timer cancel chan struct{} // Must be non-nil } func makePipeDeadline() pipeDeadline { return pipeDeadline{cancel: make(chan struct{})} } // set sets the point in time when the deadline will time out. // A timeout event is signaled by closing the channel returned by waiter. // Once a timeout has occurred, the deadline can be refreshed by specifying a // t value in the future. // // A zero value for t prevents timeout. func (d *pipeDeadline) set(t time.Time) { d.mu.Lock() defer d.mu.Unlock() // deadline closed if d.cancel == nil { return } if d.timer != nil && !d.timer.Stop() { <-d.cancel // Wait for the timer callback to finish and close cancel } d.timer = nil // Time is zero, then there is no deadline. closed := isClosedChan(d.cancel) if t.IsZero() { if closed { d.cancel = make(chan struct{}) } return } // Time in the future, setup a timer to cancel in the future. if dur := time.Until(t); dur > 0 { if closed { d.cancel = make(chan struct{}) } d.timer = time.AfterFunc(dur, func() { close(d.cancel) }) return } // Time in the past, so close immediately. if !closed { close(d.cancel) } } // wait returns a channel that is closed when the deadline is exceeded. func (d *pipeDeadline) wait() chan struct{} { d.mu.Lock() defer d.mu.Unlock() return d.cancel } // close closes, the deadline. Any future calls to `set` will do nothing. func (d *pipeDeadline) close() { d.mu.Lock() defer d.mu.Unlock() if d.timer != nil && !d.timer.Stop() { <-d.cancel // Wait for the timer callback to finish and close cancel } d.timer = nil d.cancel = nil } func isClosedChan(c <-chan struct{}) bool { select { case <-c: return true default: return false } }