v0.3.0 99dc266

client_transport_errors.go RawHistoryFolder

1package daemon
2
3import "github.com/bnema/vev/internal/ports"
4
5// clientGone detaches ac if it is still the session's current client. The
6// session remains registered and headless after the client is gone.
7func (d *Daemon) clientGone(sess *session, ac *attachedClient, failed ports.Transport, explicit bool) {
8	d.clientGoneWithNotice(sess, ac, failed, explicit, true)
9}
10
11func (d *Daemon) clientGoneWithoutNotice(sess *session, ac *attachedClient, failed ports.Transport, explicit bool) {
12	d.clientGoneWithNotice(sess, ac, failed, explicit, false)
13}
14
15func (d *Daemon) clientGoneWithNotice(sess *session, ac *attachedClient, failed ports.Transport, explicit, notice bool) {
16	if sess == nil || ac == nil {
17		return
18	}
19	expected := transportSnapshot{}
20	if failed != nil {
21		expected = ac.transportSnapshot()
22		if expected.transport != failed {
23			return // stale connection loop; a newer transport owns this client
24		}
25	}
26	// Advertise parking before detach so IntentResume never observes both the
27	// live seat and parking/parked registries empty for a still-valid token.
28	var parkingToken uint64
29	if !explicit {
30		parkingToken = d.markParkingInFlight(sess, ac)
31	}
32	if d.beforeClientGoneDetach != nil {
33		d.beforeClientGoneDetach()
34	}
35	if !d.detachIfCurrentTransport(sess, ac, expected) {
36		d.clearParkingInFlightIfAbandoned(sess, ac, parkingToken)
37		return // displaced, or the link was rebound after the precheck
38	}
39	d.finishClientGone(sess, ac, failed, explicit, notice)
40}
41
42func (d *Daemon) clientGoneForAttachment(token attachmentConnectionToken, explicit bool) bool {
43	if token.effect == nil {
44		return false
45	}
46	token.effect.bindActionEnd(d, "detach")
47	token.effect.End()
48	sess := token.sess
49	if sess == nil {
50		return false
51	}
52	var parkingToken uint64
53	if !explicit {
54		parkingToken = d.markParkingInFlight(sess, token.ac)
55	}
56	if !d.detachIfAttachmentCurrent(token) {
57		d.clearParkingInFlightIfAbandoned(sess, token.ac, parkingToken)
58		return false
59	}
60	d.finishClientGone(sess, token.ac, token.transport.transport, explicit, true)
61	return true
62}
63
64func (d *Daemon) finishClientGone(sess *session, ac *attachedClient, failed ports.Transport, explicit, notice bool) {
65	if sess == nil || ac == nil {
66		return
67	}
68	if d.afterClientGoneDetach != nil {
69		d.afterClientGoneDetach()
70	}
71	if rc := sess.renderCoordinator(); rc != nil {
72		rc.noteDetach(ac)
73	}
74	d.recalculateSessionGeometryAndInvalidate(sess, nil, "client_transport_errors.go")
75	sess.mu.Lock()
76	ephemeral := sess.ephemeral
77	name := sess.name
78	sess.mu.Unlock()
79	if !ephemeral {
80		d.refreshSessionCwd(sess)
81	}
82	d.log.Info("client detach begin", "session", name, "explicit", explicit, "ephemeral", ephemeral)
83	oldTr := failed
84	if oldTr == nil {
85		oldTr = ac.transport()
86	}
87	if !explicit && d.parkAttachment(sess, ac) {
88		_ = ac.closeCapturedTransport(oldTr)
89		d.log.Info("client parked", "session", name)
90		return
91	}
92	// Explicit winners (and non-explicit park failures) must drop any same-
93	// attachment parking marker left by a raced non-explicit teardown so
94	// IntentResume waiters are not stranded on a never-published park.
95	d.clearParkingInFlight(d.resumeTokenSnapshot(ac), ac)
96	d.closePicker(ac)
97
98	d.resetScreenDefaultColors(sess)
99	if explicit && notice {
100		// Synchronous so the ack is delivered before the transport closes
101		// (the client is actively awaiting it), but deadline-bounded so a
102		// wedged client cannot pin this conn handler and hang Serve's
103		// connWg.Wait.
104		d.boundedSend(ac, frameDetached(ports.ReasonDetach))
105	}
106	_ = ac.closeCapturedTransport(oldTr)
107	d.log.Info("client detached", "session", name, "explicit", explicit)
108}
109
110// detachOnSendError drops a client whose transport failed, leaving the session
111// registered and headless.
112func (d *Daemon) detachOnSendError(sess *session, ac *attachedClient, failed ports.Transport) {
113	expected := transportSnapshot{}
114	if failed != nil {
115		expected = ac.transportSnapshot()
116		if expected.transport != failed {
117			return
118		}
119	}
120	parkingToken := d.markParkingInFlight(sess, ac)
121	if d.detachIfCurrentTransport(sess, ac, expected) {
122		d.finishSendErrorDetach(sess, ac, failed)
123		return
124	}
125	d.clearParkingInFlightIfAbandoned(sess, ac, parkingToken)
126}
127
128func (d *Daemon) detachOnAttachmentSendError(token attachmentConnectionToken, failed ports.Transport) {
129	d.detachOnAttachmentSendErrorUntil(token, failed, nil)
130}
131
132func (d *Daemon) detachOnAttachmentSendErrorUntil(token attachmentConnectionToken, failed ports.Transport, done func() <-chan struct{}) {
133	// A delayed sender may report after the client has rebound. Only the exact
134	// transport captured by this attachment is allowed to detach either lifecycle.
135	if failed == nil || failed != token.transport.transport {
136		return
137	}
138	sess := token.sess
139	if sess == nil {
140		return
141	}
142	parkingToken := d.markParkingInFlight(sess, token.ac)
143	if d.detachIfAttachmentCurrentUntil(token, done) {
144		d.finishSendErrorDetach(sess, token.ac, failed)
145		return
146	}
147	d.clearParkingInFlightIfAbandoned(sess, token.ac, parkingToken)
148}
149
150// reserveAttachmentSendErrorCleanup accounts for cleanup before End releases the
151// attachment gate. This closes the WaitGroup Add/Wait race with terminal teardown;
152// the returned launch function must be invoked immediately after ticket End.
153func (d *Daemon) reserveAttachmentSendErrorCleanup(token attachmentConnectionToken, failed ports.Transport) func() {
154	d.attachmentCleanupWg.Add(1)
155	return func() {
156		go func() {
157			defer d.attachmentCleanupWg.Done()
158			if d.afterAttachmentSendErrorCleanup != nil {
159				defer d.afterAttachmentSendErrorCleanup()
160			}
161			if d.beforeAttachmentSendErrorCleanup != nil {
162				d.beforeAttachmentSendErrorCleanup(token)
163			}
164			deadline := newAttachmentEffectDrainDeadline(d.clock)
165			defer deadline.stop()
166			d.detachOnAttachmentSendErrorUntil(token, failed, deadline.Done)
167		}()
168	}
169}
170
171func (d *Daemon) finishSendErrorDetach(sess *session, ac *attachedClient, failed ports.Transport) {
172	name := sess.nameSnapshot()
173	if rc := sess.renderCoordinator(); rc != nil {
174		rc.noteDetach(ac)
175	}
176	d.recalculateSessionGeometryAndInvalidate(sess, nil, "client_transport_errors.go")
177	if d.parkAttachment(sess, ac) {
178		_ = ac.closeCapturedTransport(failed)
179		d.log.Warn("parked client after send error", "session", name)
180		return
181	}
182	d.clearParkingInFlight(d.resumeTokenSnapshot(ac), ac)
183	d.closePicker(ac)
184	d.resetScreenDefaultColors(sess)
185	_ = ac.closeCapturedTransport(failed)
186	d.log.Warn("detached client after send error", "session", name)
187}

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