after_frame.go RawHistoryFolder

1package daemon
2
3import "sync"
4
5// afterFrameQueue orders a control after the attachment's next composed frame.
6//
7// A client overlay (the session or move picker) suppresses attachment output
8// from the moment it opens, so the frame that erases a daemon overlay closed by
9// the same action must reach the client first. Queued work is released once a
10// frame captured after its registration has been emitted. The transport is one
11// ordered stream, so that frame is queued on the wire before anything the work
12// sends.
13type afterFrameQueue struct {
14	mu       sync.Mutex
15	captures uint64 // captures started so far
16	pending  []afterFrameWork
17	ready    []func()
18}
19
20type afterFrameWork struct {
21	minCapture uint64 // first capture that observes state at registration
22	run        func()
23}
24
25func (q *afterFrameQueue) add(run func()) {
26	q.mu.Lock()
27	q.pending = append(q.pending, afterFrameWork{minCapture: q.captures + 1, run: run})
28	q.mu.Unlock()
29}
30
31// beginCapture numbers one frame capture. Paint calls it under sendMu before
32// snapshotting overlays, so a capture numbered after a registration observes
33// every state change that preceded it.
34func (q *afterFrameQueue) beginCapture() uint64 {
35	q.mu.Lock()
36	defer q.mu.Unlock()
37	q.captures++
38	return q.captures
39}
40
41// emitted releases the work satisfied by the emitted capture.
42func (q *afterFrameQueue) emitted(capture uint64) {
43	if capture == 0 {
44		return
45	}
46	q.mu.Lock()
47	defer q.mu.Unlock()
48	kept := q.pending[:0]
49	for _, work := range q.pending {
50		if work.minCapture <= capture {
51			q.ready = append(q.ready, work.run)
52		} else {
53			kept = append(kept, work)
54		}
55	}
56	clear(q.pending[len(kept):])
57	q.pending = kept
58}
59
60// runReady starts released work on its own goroutine: the painting goroutine
61// (often a render wake) must not wait on picker projections or a blocking
62// control send, and its caller may hold dispatch locks.
63func (q *afterFrameQueue) runReady() {
64	q.mu.Lock()
65	ready := q.ready
66	q.ready = nil
67	q.mu.Unlock()
68	for _, run := range ready {
69		go run()
70	}
71}
72
73// afterNextFrame runs work with a fresh effect once a frame composed after this
74// call reached the client. A transient freeze of the same attachment delays the
75// work until the freeze ends (the frame is already on the wire); a changed
76// capability (detach, suspension, transport replacement, session switch) or the
77// session ending drops it.
78func (d *Daemon) afterNextFrame(sess *session, ac *attachedClient, effect *attachmentEffect, work func(*attachmentEffect)) {
79	d.queueAfterNextFrame(ac, effect, work)
80	d.invalidateRender(sess, ac, true, "after_frame.go")
81}
82
83// queueAfterNextFrame registers work without requesting the frame.
84func (d *Daemon) queueAfterNextFrame(ac *attachedClient, effect *attachmentEffect, work func(*attachmentEffect)) {
85	capability := effect.capability()
86	ac.afterFrame.add(func() {
87		fresh, admitted := waitAttachmentEffect(capability)
88		if !admitted {
89			if d.log != nil {
90				d.log.Debug("dropping after-frame work for a replaced attachment")
91			}
92			return
93		}
94		defer fresh.End()
95		work(fresh)
96	})
97}
98
99// waitAttachmentEffect admits capability, waiting out a transient freeze while
100// it stays current. It gives up once the capability is replaced or its session
101// ends.
102func waitAttachmentEffect(capability attachmentCapability) (*attachmentEffect, bool) {
103	ac := capability.ac
104	if ac == nil || capability.sess == nil {
105		return nil, false
106	}
107	var done <-chan struct{}
108	if ctx := capability.sess.ctx; ctx != nil {
109		done = ctx.Done()
110	}
111	for {
112		// Take the change signal before trying, so a thaw between the failed
113		// admission and the wait is never missed.
114		g := &ac.lifecycle
115		g.mu.Lock()
116		g.initLocked()
117		changed := g.changed
118		g.mu.Unlock()
119		if fresh, admitted := ac.beginAttachmentEffect(capability); admitted {
120			return fresh, true
121		}
122		if !capability.current() {
123			return nil, false
124		}
125		select {
126		case <-changed:
127		case <-done:
128			return nil, false
129		}
130	}
131}

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