attachment_connection.go RawHistoryFolder
1package daemon
2
3import "github.com/bnema/vev/internal/ports"
4
5// attachmentConnectionToken is the exact capability for one registered
6// attachment connection incarnation. It is invalid as soon as any of the
7// session, attachment membership, connection generation, transport link, or
8// coordinator lease changes.
9type attachmentConnectionToken struct {
10 sess *session
11 ac *attachedClient
12 generation uint64
13 transport transportSnapshot
14 lease *attachmentLease
15 effect *attachmentEffectTicket
16 // rebase marks a cross-session move whose first paint starts a dependency-
17 // free output chain after publication releases architecture locks.
18 rebase bool
19}
20
21func attachmentToken(entry *session, ac *attachedClient, tr ports.Transport) attachmentConnectionToken {
22 if entry == nil || entry.core() == nil || ac == nil || tr == nil {
23 return attachmentConnectionToken{}
24 }
25 // Capture one concrete link incarnation before taking the session lock. The
26 // same snapshot is revalidated below; do not take a second snapshot and
27 // accidentally bind a replacement link.
28 transport := ac.transportSnapshot()
29 if transport.transport != tr {
30 return attachmentConnectionToken{}
31 }
32 if ac.beforeAttachmentTokenValidation != nil {
33 ac.beforeAttachmentTokenValidation()
34 }
35 core := entry.core()
36 core.mu.Lock()
37 if !attachmentRegisteredLocked(entry, ac) || !ac.transportSnapshotCurrent(transport) {
38 core.mu.Unlock()
39 return attachmentConnectionToken{}
40 }
41 generation := ac.connectionGeneration.Load()
42 token := attachmentConnectionToken{
43 sess: entry,
44 ac: ac,
45 generation: generation,
46 transport: transport,
47 }
48 if rc := core.coordinator.Load(); rc != nil {
49 token.lease = rc.attachmentLease(ac)
50 }
51 core.mu.Unlock()
52 if !token.current() {
53 return attachmentConnectionToken{}
54 }
55 ac.bootstrapAttachmentCapability(token)
56 return token
57}
58
59func (s *session) attachmentToken(ac *attachedClient, tr ports.Transport) attachmentConnectionToken {
60 return attachmentToken(s, ac, tr)
61}
62
63// current validates every immutable identity captured by the token. Membership
64// is the sole authority; there is no owner, replacement, or compatibility role.
65func (t attachmentConnectionToken) current() bool {
66 return t.sess != nil && t.ac != nil &&
67 t.ac.connectionGeneration.Load() == t.generation &&
68 t.ac.currentAttachmentSession() == t.sess &&
69 t.ac.transportSnapshotCurrent(t.transport) &&
70 attachmentRegistered(t.sess, t.ac)
71}
72
73// attachmentCurrent additionally validates the optional coordinator lease used
74// by render and resize effects. Attachments without that optional lease still
75// own their independent connection lifecycle.
76func (t attachmentConnectionToken) attachmentCurrent() bool {
77 if !t.current() || t.ac.currentAttachmentSession() != t.sess {
78 return false
79 }
80 if t.lease == nil {
81 return true
82 }
83 rc := t.sess.core().coordinator.Load()
84 return rc != nil && t.lease.attachment == t.ac && rc.leaseCurrent(t.lease, true)
85}
86
87func (t attachmentConnectionToken) attachmentEffectCurrent() bool {
88 if t.effect != nil && !t.effect.ended.Load() {
89 return true
90 }
91 return t.attachmentCurrent()
92}
93
94func beginAttachmentLeaseEffect(sess *session, ac *attachedClient, lease *attachmentLease) (*attachmentEffectTicket, bool) {
95 if sess == nil || ac == nil || lease == nil {
96 return nil, false
97 }
98 token := attachmentToken(sess, ac, ac.transport())
99 token.lease = lease
100 return ac.beginAttachmentEffect(token)
101}
102
103func (t *attachmentConnectionToken) endAttachmentEffect() {
104 if t == nil || t.effect == nil {
105 return
106 }
107 t.effect.End()
108 t.effect = nil
109}
110
111// attachmentEffectCurrentSessionLocked validates authority at a session-owned
112// mutation boundary where sess.core().mu is already held. A live effect ticket
113// preserves the exact capability it captured for the effect because attachment
114// transitions drain the gate before changing identity. This differs from
115// attachmentCurrent, which revalidates the attachment's currently published identity.
116func (t attachmentConnectionToken) attachmentEffectCurrentSessionLocked() bool {
117 if t.effect != nil && !t.effect.ended.Load() {
118 return true
119 }
120 if t.sess == nil || t.ac == nil ||
121 t.ac.connectionGeneration.Load() != t.generation ||
122 !t.ac.transportSnapshotCurrent(t.transport) ||
123 t.ac.currentAttachmentSession() != t.sess ||
124 !attachmentRegisteredLocked(t.sess, t.ac) {
125 return false
126 }
127 if t.lease == nil {
128 return true
129 }
130 rc := t.sess.core().coordinator.Load()
131 return rc != nil && t.lease.attachment == t.ac && rc.leaseCurrent(t.lease, true)
132}
133
134func (t attachmentConnectionToken) sendControl(frame ports.Frame) error {
135 if t.ac == nil || t.transport.transport == nil {
136 return errAttachmentTransition
137 }
138 t.ac.sendMu.Lock()
139 defer t.ac.sendMu.Unlock()
140 if t.effect == nil || t.effect.ended.Load() || !t.ac.transportSnapshotCurrent(t.transport) ||
141 !t.effect.beginTransportSend(t.transport) {
142 return errAttachmentTransition
143 }
144 err := t.transport.transport.Send(frame)
145 if err != nil {
146 t.effect.reportTransportFailure(t.transport)
147 }
148 t.effect.endTransportSend()
149 return err
150}