File: xtime.go

package info (click to toggle)
golang-github-bradenaw-juniper 0.15.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 872 kB
  • sloc: sh: 27; makefile: 2
file content (143 lines) | stat: -rw-r--r-- 3,313 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Package xtime contains extensions to the standard library package time.
package xtime

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

type DeadlineTooSoonError struct {
	remaining time.Duration
	d         time.Duration
}

func (err DeadlineTooSoonError) Error() string {
	return fmt.Sprintf(
		"not enough time remaining in context: %s remaining for %s sleep",
		err.remaining,
		err.d,
	)
}

// SleepContext pauses the current goroutine for at least the duration d and returns nil, unless ctx
// expires in the mean time in which case it returns ctx.Err().
//
// A negative or zero duration causes SleepContext to return nil immediately.
//
// If there is less than d left until ctx's deadline, returns DeadlineTooSoonError immediately.
func SleepContext(ctx context.Context, d time.Duration) error {
	if d <= 0 {
		return nil
	}
	deadline, ok := ctx.Deadline()
	if ok {
		remaining := time.Until(deadline)
		if remaining > d {
			return DeadlineTooSoonError{remaining: remaining, d: d}
		}
	}
	t := time.NewTimer(d)
	select {
	case <-ctx.Done():
		t.Stop()
		return ctx.Err()
	case <-t.C:
		return nil
	}
}

// A JitterTicker holds a channel that delivers "ticks" of a clock at intervals.
type JitterTicker struct {
	C <-chan time.Time

	c      chan time.Time
	m      sync.Mutex
	d      time.Duration
	gen    int
	jitter time.Duration
	timer  *time.Timer
}

// NewJitterTicker is similar to time.NewTicker, but jitters the ticks by the given amount. That is,
// each tick will be d+/-jitter apart.
//
// The duration d must be greater than zero and jitter must be less than d; if not, NewJitterTicker
// will panic.
func NewJitterTicker(d time.Duration, jitter time.Duration) *JitterTicker {
	if d <= 0 {
		panic("non-positive interval for NewJitterTicker")
	}
	if jitter >= d {
		panic("jitter greater than d")
	}

	c := make(chan time.Time, 1)
	t := &JitterTicker{
		C:      c,
		c:      c,
		d:      d,
		jitter: jitter,
	}
	t.m.Lock()
	t.schedule()
	t.m.Unlock()
	return t
}

func (t *JitterTicker) schedule() {
	if t.timer != nil {
		t.timer.Stop()
	}
	next := t.d + time.Duration(rand.Int63n(int64(t.jitter*2))) - (t.jitter)

	// To prevent a latent goroutine already spawned but not yet running the below function from
	// delivering a tick after Stop/Reset.
	t.gen++
	gen := t.gen

	t.timer = time.AfterFunc(next, func() {
		t.m.Lock()
		if t.gen == gen {
			select {
			case t.c <- time.Now():
			default:
			}
			t.schedule()
		}
		t.m.Unlock()
	})
}

// Reset stops the ticker and resets its period to be the specified duration and jitter. The next
// tick will arrive after the new period elapses.
//
// The duration d must be greater than zero and jitter must be less than d; if not, Reset will
// panic.
func (t *JitterTicker) Reset(d time.Duration, jitter time.Duration) {
	if d <= 0 {
		panic("non-positive interval for NewJitterTicker")
	}
	if jitter >= d {
		panic("jitter greater than d")
	}

	t.m.Lock()
	t.d = d
	t.jitter = jitter
	t.schedule()
	t.m.Unlock()
}

// Stop turns off the JitterTicker. After it returns, no more ticks will be sent. Stop does not
// close the channel, to prevent a concurrent goroutine reading from the channel from seeing an
// erroneous "tick".
func (t *JitterTicker) Stop() {
	t.m.Lock()
	t.timer.Stop()
	t.gen++
	t.timer = nil
	t.m.Unlock()
}