v0.32.0 0617278

heartbeat.go RawHistoryFolder

1package cef
2
3import (
4	"sync/atomic"
5	"time"
6
7	purecef "github.com/bnema/purego-cef/cef"
8
9	"github.com/bnema/dumber/internal/logging"
10)
11
12const (
13	cefHeartbeatInterval    = 1 * time.Second
14	cefHeartbeatLogEvery    = 10 * time.Second
15	cefHeartbeatStopTimeout = 2 * time.Second
16)
17
18type cefThreadHeartbeatState struct {
19	seq            atomic.Uint64
20	lastPostUnixNS atomic.Int64
21	lastAckUnixNS  atomic.Int64
22	lastLatencyNS  atomic.Int64
23	lastPostResult atomic.Int32
24	lastLogUnixNS  atomic.Int64
25	inFlight       atomic.Bool
26}
27
28func (s *cefThreadHeartbeatState) snapshot(now time.Time) cefHeartbeatSnapshot {
29	if s == nil {
30		return cefHeartbeatSnapshot{}
31	}
32	lastPost := unixNSTime(s.lastPostUnixNS.Load())
33	lastAck := unixNSTime(s.lastAckUnixNS.Load())
34	snap := cefHeartbeatSnapshot{
35		Seq:          s.seq.Load(),
36		PostResult:   s.lastPostResult.Load(),
37		Latency:      time.Duration(s.lastLatencyNS.Load()),
38		LastPostTime: lastPost,
39		LastAckTime:  lastAck,
40		InFlight:     s.inFlight.Load(),
41	}
42	if !lastPost.IsZero() {
43		snap.LastPostAge = now.Sub(lastPost)
44	}
45	if !lastAck.IsZero() {
46		snap.LastAckAge = now.Sub(lastAck)
47	}
48	return snap
49}
50
51type cefHeartbeatSnapshot struct {
52	Seq          uint64
53	PostResult   int32
54	Latency      time.Duration
55	LastPostTime time.Time
56	LastAckTime  time.Time
57	LastPostAge  time.Duration
58	LastAckAge   time.Duration
59	InFlight     bool
60}
61
62func (e *Engine) startCEFHeartbeat() {
63	if e == nil || e.ctx == nil || e.cefHeartbeatStop != nil {
64		return
65	}
66	stop := make(chan struct{})
67	done := make(chan struct{})
68	e.cefHeartbeatStop = stop
69	e.cefHeartbeatDone = done
70	go e.cefHeartbeatLoop(stop, done)
71}
72
73func (e *Engine) stopCEFHeartbeat() {
74	if e == nil || e.cefHeartbeatStop == nil {
75		return
76	}
77	stop := e.cefHeartbeatStop
78	done := e.cefHeartbeatDone
79	e.cefHeartbeatStop = nil
80	e.cefHeartbeatDone = nil
81	close(stop)
82	if done == nil {
83		return
84	}
85	select {
86	case <-done:
87	case <-time.After(cefHeartbeatStopTimeout):
88		if e.ctx != nil {
89			logging.FromContext(e.ctx).Warn().Msg("cef: heartbeat stop timed out")
90		}
91	}
92}
93
94func (e *Engine) cefHeartbeatLoop(stop <-chan struct{}, done chan<- struct{}) {
95	defer close(done)
96	ticker := time.NewTicker(cefHeartbeatInterval)
97	defer ticker.Stop()
98	for {
99		select {
100		case <-stop:
101			return
102		case now := <-ticker.C:
103			e.postCEFThreadHeartbeat("ui", purecef.ThreadIDTidUi, &e.cefUIHeartbeat, now)
104			e.postCEFThreadHeartbeat("io", purecef.ThreadIDTidIo, &e.cefIOHeartbeat, now)
105		}
106	}
107}
108
109func (e *Engine) postCEFThreadHeartbeat(name string, threadID purecef.ThreadID, state *cefThreadHeartbeatState, now time.Time) {
110	if e == nil || state == nil {
111		return
112	}
113	seq := state.seq.Add(1)
114	postedAt := now
115	state.lastPostUnixNS.Store(postedAt.UnixNano())
116	state.inFlight.Store(true)
117	task := cefNewTask(cefTaskFunc(func() {
118		ackedAt := time.Now()
119		state.lastAckUnixNS.Store(ackedAt.UnixNano())
120		state.lastLatencyNS.Store(ackedAt.Sub(postedAt).Nanoseconds())
121		state.inFlight.Store(false)
122	}))
123	if task == nil {
124		state.lastPostResult.Store(0)
125		state.inFlight.Store(false)
126		return
127	}
128	result := cefPostTask(threadID, task)
129	state.lastPostResult.Store(result)
130	if result != 1 {
131		state.inFlight.Store(false)
132	}
133	lastLog := unixNSTime(state.lastLogUnixNS.Load())
134	if result != 1 || lastLog.IsZero() || now.Sub(lastLog) >= cefHeartbeatLogEvery {
135		state.lastLogUnixNS.Store(now.UnixNano())
136		snap := state.snapshot(now)
137		logging.FromContext(e.ctx).Debug().
138			Str("thread", name).
139			Uint64("seq", seq).
140			Int32("post_result", result).
141			Int64("last_ack_age_ms", durationMillis(snap.LastAckAge)).
142			Int64("latency_ms", durationMillis(snap.Latency)).
143			Bool("in_flight", snap.InFlight).
144			Msg("cef: thread heartbeat")
145	}
146}
147
148func (e *Engine) cefHeartbeatSnapshots(now time.Time) (cefHeartbeatSnapshot, cefHeartbeatSnapshot) {
149	if e == nil {
150		return cefHeartbeatSnapshot{}, cefHeartbeatSnapshot{}
151	}
152	return e.cefUIHeartbeat.snapshot(now), e.cefIOHeartbeat.snapshot(now)
153}
154
155func (wv *WebView) cefHeartbeatSnapshots(now time.Time) (cefHeartbeatSnapshot, cefHeartbeatSnapshot) {
156	if wv == nil || wv.engine == nil {
157		return cefHeartbeatSnapshot{}, cefHeartbeatSnapshot{}
158	}
159	return wv.engine.cefHeartbeatSnapshots(now)
160}
161
162func unixNSTime(ns int64) time.Time {
163	if ns <= 0 {
164		return time.Time{}
165	}
166	return time.Unix(0, ns)
167}
168
169func durationMillis(d time.Duration) int64 {
170	if d <= 0 {
171		return 0
172	}
173	return d.Milliseconds()
174}

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 8

Tags 42

↑↓ moveenter openesc close