terminal.go RawHistoryFolder

1package daemonmux
2
3import (
4	"errors"
5	"sync"
6
7	"github.com/bnema/vev/internal/domain"
8)
9
10// ErrTerminalFailure is the stable package sentinel published by Fail when it
11// is called with a nil cause. A failure always pairs a non-nil Err() with a
12// nonzero RemoteFailureKind, so Fail substitutes this sentinel for a nil cause
13// rather than publish an inconsistent terminal outcome.
14var ErrTerminalFailure = errors.New("daemonmux: terminal failure without a cause")
15
16// terminalState is the publish-once terminal authority shared by a physical
17// daemon connection and every logical stream it owns. It mirrors the
18// Done/Err/FailureKind contract of ports.BrokerPhysicalConnection: once the
19// connection reaches a terminal outcome, every getter returns the same stable
20// values and Done is closed exactly once, independent of reads.
21//
22// Exactly one publication wins. The first concurrent Fail or Close decides Err
23// and FailureKind; later publishers observe the winner and never change it. An
24// orderly local Close publishes a nil error and RemoteFailureNone. A failure
25// publishes its cause and a nonzero valid RemoteFailureKind, preserves the
26// error identity for errors.Is/As, and normalizes any kind outside the
27// transport-failure taxonomy (including RemoteFailureNone) to
28// RemoteFailureTransport. A failure never publishes a nil error: a nil cause is
29// replaced with the ErrTerminalFailure sentinel.
30type terminalState struct {
31	once sync.Once
32	done chan struct{}
33	err  error
34	kind domain.RemoteFailureKind
35}
36
37// newTerminalState returns an open terminal state. Done stays open until a
38// publisher wins.
39func newTerminalState() *terminalState {
40	return &terminalState{done: make(chan struct{})}
41}
42
43// Done returns a channel closed exactly once when the terminal outcome is
44// published. The channel is stable and this method is safe before or after
45// publication.
46func (s *terminalState) Done() <-chan struct{} { return s.done }
47
48// Err returns the published failure cause: nil while open and for an orderly
49// local Close. The result is stable once Done is closed.
50func (s *terminalState) Err() error {
51	select {
52	case <-s.done:
53		return s.err
54	default:
55		return nil
56	}
57}
58
59// FailureKind returns the published failure classification: RemoteFailureNone
60// while open and for an orderly local Close. The result is stable once Done is
61// closed.
62func (s *terminalState) FailureKind() domain.RemoteFailureKind {
63	select {
64	case <-s.done:
65		return s.kind
66	default:
67		return domain.RemoteFailureNone
68	}
69}
70
71// Fail publishes a failure terminal outcome. It wins only while the state is
72// open, so at most one publisher ever applies. The kind must be a nonzero
73// valid RemoteFailureKind; any other value, including RemoteFailureNone, is
74// normalized to RemoteFailureTransport. A non-nil error is stored verbatim so
75// callers keep errors.Is/As identity; a nil error is replaced with the
76// ErrTerminalFailure package sentinel so a failure never publishes a nil Err()
77// alongside a nonzero kind.
78func (s *terminalState) Fail(kind domain.RemoteFailureKind, err error) {
79	s.publishFail(kind, err)
80}
81
82// publishFail applies a failure outcome exactly once and reports whether this
83// call won the publication. It is the primitive behind Fail and the callers
84// that must know they are the one publisher - for example, so a stream signals
85// its peer with exactly one Reset instead of one per observer. The first
86// publisher's values win; a later caller observes false and must not apply its
87// own outcome.
88func (s *terminalState) publishFail(kind domain.RemoteFailureKind, err error) bool {
89	if kind < domain.RemoteFailureTransport || kind > domain.RemoteFailureInvalidResponse {
90		kind = domain.RemoteFailureTransport
91	}
92	if err == nil {
93		err = ErrTerminalFailure
94	}
95	won := false
96	s.once.Do(func() {
97		s.err = err
98		s.kind = kind
99		close(s.done)
100		won = true
101	})
102	return won
103}
104
105// Close publishes an orderly local terminal outcome: a nil error and
106// RemoteFailureNone. It wins only while the state is open.
107func (s *terminalState) Close() {
108	s.publish(nil, domain.RemoteFailureNone)
109}
110
111// publish applies the first outcome exactly once and then closes Done. The
112// winning values are stored before close(done), so every reader that observes
113// the closed channel sees them.
114func (s *terminalState) publish(err error, kind domain.RemoteFailureKind) {
115	s.once.Do(func() {
116		s.err = err
117		s.kind = kind
118		close(s.done)
119	})
120}

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