Initial QSfera import
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Roland Singer
|
||||
|
||||
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.
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# Go Timer implementation with a fixed Reset behavior
|
||||
|
||||
[](https://godoc.org/github.com/desertbit/timer)
|
||||
[](https://goreportcard.com/report/github.com/desertbit/timer)
|
||||
|
||||
This is a lightweight timer implementation which is a drop-in replacement for
|
||||
Go's Timer. Reset behaves as one would expect and drains the timer.C channel automatically.
|
||||
The core design of this package is similar to the original runtime timer implementation.
|
||||
|
||||
These two lines are equivalent except for saving some garbage:
|
||||
|
||||
```go
|
||||
t.Reset(x)
|
||||
|
||||
t := timer.NewTimer(x)
|
||||
```
|
||||
|
||||
See issues:
|
||||
- https://github.com/golang/go/issues/11513
|
||||
- https://github.com/golang/go/issues/14383
|
||||
- https://github.com/golang/go/issues/12721
|
||||
- https://github.com/golang/go/issues/14038
|
||||
- https://groups.google.com/forum/#!msg/golang-dev/c9UUfASVPoU/tlbK2BpFEwAJ
|
||||
- http://grokbase.com/t/gg/golang-nuts/1571eh3tv7/go-nuts-reusing-time-timer
|
||||
|
||||
Quote from the [Timer Go doc reference](https://golang.org/pkg/time/#Timer):
|
||||
|
||||
>Reset changes the timer to expire after duration d.
|
||||
It returns true if the timer had been active, false if the timer had
|
||||
expired or been stopped.
|
||||
|
||||
> To reuse an active timer, always call its Stop method first and—if it had
|
||||
expired—drain the value from its channel. For example: [...]
|
||||
This should not be done concurrent to other receives from the Timer's channel.
|
||||
|
||||
> Note that it is not possible to use Reset's return value correctly, as there
|
||||
is a race condition between draining the channel and the new timer expiring.
|
||||
Reset should always be used in concert with Stop, as described above.
|
||||
The return value exists to preserve compatibility with existing programs.
|
||||
|
||||
## Broken behavior sample
|
||||
|
||||
### Sample 1
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := time.Now()
|
||||
|
||||
// Start a new timer with a timeout of 1 second.
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
|
||||
// Wait for 2 seconds.
|
||||
// Meanwhile the timer fired and filled the channel.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Reset the timer. This should act exactly as creating a new timer.
|
||||
timer.Reset(1 * time.Second)
|
||||
|
||||
// However this will fire immediately, because the channel was not drained.
|
||||
// See issue: https://github.com/golang/go/issues/11513
|
||||
<-timer.C
|
||||
|
||||
if int(time.Since(start).Seconds()) != 3 {
|
||||
log.Fatalf("took ~%v seconds, should be ~3 seconds\n", int(time.Since(start).Seconds()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sample 2
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
keepaliveInterval = 2 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
resetC = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
func main() {
|
||||
go keepaliveLoop()
|
||||
|
||||
// Sample routine triggering the reset.
|
||||
// Example: this could be due to incoming peer requests and
|
||||
// a keepalive check should be reset to the max keepalive timeout.
|
||||
for i := 0; i < 1000; i++ {
|
||||
time.Sleep(time.Millisecond)
|
||||
resetKeepalive()
|
||||
}
|
||||
}
|
||||
|
||||
func resetKeepalive() {
|
||||
// Don't block if there is already a reset request.
|
||||
select {
|
||||
case resetC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func keepaliveLoop() {
|
||||
t := time.NewTimer(keepaliveInterval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-resetC:
|
||||
time.Sleep(3 * time.Millisecond) // Simulate some reset work...
|
||||
t.Reset(keepaliveInterval)
|
||||
case <-t.C:
|
||||
ping()
|
||||
t.Reset(keepaliveInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ping() {
|
||||
panic("ping must not be called in this example")
|
||||
}
|
||||
```
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Package timer is a Go timer implementation with a fixed Reset behavior.
|
||||
package timer
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// The Timer type represents a single event. When the Timer expires,
|
||||
// the current time will be sent on C, unless the Timer was created by AfterFunc.
|
||||
// A Timer must be created with NewTimer. NewStoppedTimer or AfterFunc.
|
||||
type Timer struct {
|
||||
C <-chan time.Time
|
||||
|
||||
i int // heap index.
|
||||
when int64 // Timer wakes up at when.
|
||||
|
||||
// f is called in a locked context on timeout. This function must not block
|
||||
// and must behave well-defined.
|
||||
f func(t *time.Time)
|
||||
|
||||
// reset is called in a locked context. This function must not block
|
||||
// and must behave well-defined.
|
||||
reset func()
|
||||
}
|
||||
|
||||
// NewTimer creates a new Timer that will send the current time on its
|
||||
// channel after at least duration d.
|
||||
func NewTimer(d time.Duration) *Timer {
|
||||
t := NewStoppedTimer()
|
||||
addTimer(t, d)
|
||||
return t
|
||||
}
|
||||
|
||||
// NewStoppedTimer creates a new stopped Timer.
|
||||
func NewStoppedTimer() *Timer {
|
||||
c := make(chan time.Time, 1)
|
||||
t := &Timer{
|
||||
C: c,
|
||||
f: func(t *time.Time) {
|
||||
// Don't block.
|
||||
select {
|
||||
case c <- *t:
|
||||
default:
|
||||
}
|
||||
},
|
||||
reset: func() {
|
||||
// Empty the channel if filled.
|
||||
select {
|
||||
case <-c:
|
||||
default:
|
||||
}
|
||||
},
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop prevents the Timer from firing.
|
||||
// It returns true if the call stops the timer,
|
||||
// false if the timer has already expired or been stopped.
|
||||
// Stop does not close the channel, to prevent a read from
|
||||
// the channel succeeding incorrectly.
|
||||
func (t *Timer) Stop() (wasActive bool) {
|
||||
if t.f == nil {
|
||||
panic("timer: Stop called on uninitialized Timer")
|
||||
}
|
||||
return delTimer(t)
|
||||
}
|
||||
|
||||
// Reset changes the timer to expire after duration d.
|
||||
// It returns true if the timer had been active,
|
||||
// false if the timer had expired or been stopped.
|
||||
// The channel t.C is cleared and calling t.Reset() behaves as creating a
|
||||
// new Timer.
|
||||
func (t *Timer) Reset(d time.Duration) bool {
|
||||
if t.f == nil {
|
||||
panic("timer: Reset called on uninitialized Timer")
|
||||
}
|
||||
return resetTimer(t, d)
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package timer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.Mutex
|
||||
timers []*Timer
|
||||
rescheduleC = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
go timerRoutine()
|
||||
}
|
||||
|
||||
// when is a helper function for setting the 'when' field of a runtimeTimer.
|
||||
// It returns what the time will be, in nanoseconds, Duration d in the future.
|
||||
// If d is negative, it is ignored. If the returned value would be less than
|
||||
// zero because of an overflow, MaxInt64 is returned.
|
||||
func when(d time.Duration) int64 {
|
||||
if d <= 0 {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
t := time.Now().UnixNano() + int64(d)
|
||||
if t < 0 {
|
||||
t = 1<<63 - 1 // math.MaxInt64
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Add the timer to the heap.
|
||||
func addTimer(t *Timer, d time.Duration) {
|
||||
t.when = when(d)
|
||||
|
||||
mutex.Lock()
|
||||
addTimerLocked(t)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
func addTimerLocked(t *Timer) {
|
||||
t.i = len(timers)
|
||||
timers = append(timers, t)
|
||||
siftupTimer(t.i)
|
||||
|
||||
// Reschedule if this is the next timer in the heap.
|
||||
if t.i == 0 {
|
||||
reschedule()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete timer t from the heap.
|
||||
// It returns true if t was removed, false if t wasn't even there.
|
||||
// Do not need to update the timer routine: if it wakes up early, no big deal.
|
||||
func delTimer(t *Timer) (b bool) {
|
||||
mutex.Lock()
|
||||
b = delTimerLocked(t)
|
||||
mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Delete timer t from the heap.
|
||||
// It returns true if t was removed, false if t wasn't even there.
|
||||
// Do not need to update the timer routine: if it wakes up early, no big deal.
|
||||
func delTimerLocked(t *Timer) bool {
|
||||
// t may not be registered anymore and may have
|
||||
// a bogus i (typically 0, if generated by Go).
|
||||
// Verify it before proceeding.
|
||||
i := t.i
|
||||
last := len(timers) - 1
|
||||
if i < 0 || i > last || timers[i] != t {
|
||||
return false
|
||||
}
|
||||
if i != last {
|
||||
timers[i] = timers[last]
|
||||
timers[i].i = i
|
||||
}
|
||||
timers[last] = nil
|
||||
timers = timers[:last]
|
||||
if i != last {
|
||||
siftupTimer(i)
|
||||
siftdownTimer(i)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Reset the timer to the new timeout duration.
|
||||
// This clears the channel.
|
||||
func resetTimer(t *Timer, d time.Duration) (b bool) {
|
||||
mutex.Lock()
|
||||
b = delTimerLocked(t)
|
||||
t.reset()
|
||||
t.when = when(d)
|
||||
addTimerLocked(t)
|
||||
mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func reschedule() {
|
||||
// Do not block if there is already a pending reschedule request.
|
||||
select {
|
||||
case rescheduleC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func timerRoutine() {
|
||||
var now time.Time
|
||||
var delta int64
|
||||
var last int
|
||||
|
||||
var sleepTimerActive bool
|
||||
sleepTimer := time.NewTimer(time.Second)
|
||||
sleepTimer.Stop()
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-sleepTimer.C:
|
||||
|
||||
case <-rescheduleC:
|
||||
// If not yet received a value from sleepTimer.C, the timer must be
|
||||
// stopped and—if Stop reports that the timer expired before being
|
||||
// stopped—the channel explicitly drained.
|
||||
if !sleepTimer.Stop() && sleepTimerActive {
|
||||
<-sleepTimer.C
|
||||
}
|
||||
}
|
||||
sleepTimerActive = false
|
||||
|
||||
Reschedule:
|
||||
now = time.Now()
|
||||
|
||||
mutex.Lock()
|
||||
if len(timers) == 0 {
|
||||
mutex.Unlock()
|
||||
continue Loop
|
||||
}
|
||||
|
||||
t := timers[0]
|
||||
delta = t.when - now.UnixNano()
|
||||
|
||||
// Sleep if not expired.
|
||||
if delta > 0 {
|
||||
mutex.Unlock()
|
||||
sleepTimer.Reset(time.Duration(delta))
|
||||
sleepTimerActive = true
|
||||
continue Loop
|
||||
}
|
||||
|
||||
// Timer expired. Trigger the timer's function callback.
|
||||
t.f(&now)
|
||||
|
||||
// Remove from heap.
|
||||
last = len(timers) - 1
|
||||
if last > 0 {
|
||||
timers[0] = timers[last]
|
||||
timers[0].i = 0
|
||||
}
|
||||
timers[last] = nil
|
||||
timers = timers[:last]
|
||||
if last > 0 {
|
||||
siftdownTimer(0)
|
||||
}
|
||||
t.i = -1 // mark as removed
|
||||
|
||||
mutex.Unlock()
|
||||
|
||||
// Reschedule immediately.
|
||||
goto Reschedule
|
||||
}
|
||||
}
|
||||
|
||||
// Heap maintenance algorithms.
|
||||
// Based on golang source /runtime/time.go
|
||||
|
||||
func siftupTimer(i int) {
|
||||
tmp := timers[i]
|
||||
when := tmp.when
|
||||
|
||||
var p int
|
||||
for i > 0 {
|
||||
p = (i - 1) / 4 // parent
|
||||
if when >= timers[p].when {
|
||||
break
|
||||
}
|
||||
timers[i] = timers[p]
|
||||
timers[i].i = i
|
||||
timers[p] = tmp
|
||||
timers[p].i = p
|
||||
i = p
|
||||
}
|
||||
}
|
||||
|
||||
func siftdownTimer(i int) {
|
||||
n := len(timers)
|
||||
when := timers[i].when
|
||||
tmp := timers[i]
|
||||
for {
|
||||
c := i*4 + 1 // left child
|
||||
c3 := c + 2 // mid child
|
||||
if c >= n {
|
||||
break
|
||||
}
|
||||
w := timers[c].when
|
||||
if c+1 < n && timers[c+1].when < w {
|
||||
w = timers[c+1].when
|
||||
c++
|
||||
}
|
||||
if c3 < n {
|
||||
w3 := timers[c3].when
|
||||
if c3+1 < n && timers[c3+1].when < w3 {
|
||||
w3 = timers[c3+1].when
|
||||
c3++
|
||||
}
|
||||
if w3 < w {
|
||||
w = w3
|
||||
c = c3
|
||||
}
|
||||
}
|
||||
if w >= when {
|
||||
break
|
||||
}
|
||||
timers[i] = timers[c]
|
||||
timers[i].i = i
|
||||
timers[c] = tmp
|
||||
timers[c].i = c
|
||||
i = c
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user