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}