v0.3.0 99dc266

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}

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