clock.go RawHistoryFolder

1package quicnettest
2
3import (
4	"sort"
5	"sync"
6	"time"
7)
8
9// Clock abstracts time so simulations can be driven by a ManualClock and stay
10// reproducible without sleeping.
11type Clock interface {
12	// Now reports the current time.
13	Now() time.Time
14	// NewTimer returns a stopped-by-default timer. Callers must Stop or Reset
15	// it before selecting on C.
16	NewTimer(d time.Duration) Timer
17}
18
19// Timer is the subset of *time.Timer the proxy scheduler needs. Deadlines are
20// absolute so that a stale duration computed before a clock jump cannot delay
21// delivery.
22type Timer interface {
23	// C is the channel the timer fires on.
24	C() <-chan time.Time
25	// Stop deactivates the timer, reporting whether it was active.
26	Stop() bool
27	// ResetAt re-arms the timer at the absolute deadline, draining a pending
28	// value first. It reports whether the timer was active before the call.
29	ResetAt(deadline time.Time) bool
30}
31
32// SystemClock is a Clock backed by wall time.
33type SystemClock struct{}
34
35// Now implements Clock.
36func (SystemClock) Now() time.Time { return time.Now() }
37
38// NewTimer implements Clock.
39func (SystemClock) NewTimer(d time.Duration) Timer {
40	return &systemTimer{timer: time.NewTimer(d)}
41}
42
43type systemTimer struct{ timer *time.Timer }
44
45func (t *systemTimer) C() <-chan time.Time { return t.timer.C }
46
47func (t *systemTimer) Stop() bool { return t.timer.Stop() }
48
49func (t *systemTimer) ResetAt(deadline time.Time) bool {
50	if !t.timer.Stop() {
51		select {
52		case <-t.timer.C:
53		default:
54		}
55	}
56	return t.timer.Reset(time.Until(deadline))
57}
58
59// ManualClock is a deterministic Clock. Time only moves when Advance is called,
60// so impairment scheduling can be asserted exactly instead of waiting on wall
61// time.
62type ManualClock struct {
63	mu     sync.Mutex
64	now    time.Time
65	order  uint64
66	timers map[*manualTimer]struct{}
67}
68
69// NewManualClock returns a ManualClock positioned at start.
70func NewManualClock(start time.Time) *ManualClock {
71	return &ManualClock{now: start, timers: make(map[*manualTimer]struct{})}
72}
73
74// Now implements Clock.
75func (c *ManualClock) Now() time.Time {
76	c.mu.Lock()
77	defer c.mu.Unlock()
78	return c.now
79}
80
81// NewTimer implements Clock.
82func (c *ManualClock) NewTimer(d time.Duration) Timer {
83	t := &manualTimer{clock: c, ch: make(chan time.Time, 1)}
84	c.mu.Lock()
85	defer c.mu.Unlock()
86	t.arm(d)
87	return t
88}
89
90// Advance moves time forward by d and fires every timer that has come due.
91// Timers with equal deadlines fire in creation order, keeping the scheduler
92// deterministic. Advance panics when d is negative, because a manual clock
93// cannot un-fire timers.
94func (c *ManualClock) Advance(d time.Duration) {
95	if d < 0 {
96		panic("quicnettest: ManualClock.Advance requires a non-negative duration")
97	}
98	c.mu.Lock()
99	c.now = c.now.Add(d)
100	now := c.now
101	due := make([]*manualTimer, 0, len(c.timers))
102	for timer := range c.timers {
103		if !timer.when.After(now) {
104			due = append(due, timer)
105		}
106	}
107	sort.Slice(due, func(i, j int) bool {
108		if due[i].when.Equal(due[j].when) {
109			return due[i].order < due[j].order
110		}
111		return due[i].when.Before(due[j].when)
112	})
113	for _, timer := range due {
114		timer.active = false
115		delete(c.timers, timer)
116		select {
117		case timer.ch <- now:
118		default:
119		}
120	}
121	c.mu.Unlock()
122}
123
124type manualTimer struct {
125	clock  *ManualClock
126	ch     chan time.Time
127	when   time.Time
128	order  uint64
129	active bool
130}
131
132// arm schedules the timer relative to the clock's current time. The caller must
133// hold clock.mu.
134func (t *manualTimer) arm(d time.Duration) {
135	t.when = t.clock.now.Add(d)
136	t.order = t.clock.order
137	t.clock.order++
138	if d <= 0 {
139		t.active = false
140		select {
141		case t.ch <- t.clock.now:
142		default:
143		}
144		return
145	}
146	t.active = true
147	t.clock.timers[t] = struct{}{}
148}
149
150func (t *manualTimer) C() <-chan time.Time { return t.ch }
151
152func (t *manualTimer) Stop() bool {
153	c := t.clock
154	c.mu.Lock()
155	defer c.mu.Unlock()
156	if !t.active {
157		return false
158	}
159	t.active = false
160	delete(c.timers, t)
161	return true
162}
163
164func (t *manualTimer) ResetAt(deadline time.Time) bool {
165	c := t.clock
166	c.mu.Lock()
167	defer c.mu.Unlock()
168	wasActive := t.active
169	if t.active {
170		t.active = false
171		delete(c.timers, t)
172	}
173	select {
174	case <-t.ch:
175	default:
176	}
177	t.arm(deadline.Sub(c.now))
178	return wasActive
179}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close