v0.7.0 4c16dd8

pipe.go RawHistoryFolder

1package brokeripc
2
3import (
4	"io"
5	"sync"
6)
7
8// streamPipe is the private ordered, bounded byte channel between one broker
9// wire session and the sessionwire framing of one logical stream.
10//
11// The wire reader never blocks on a slow consumer: a chunk the peer sent is
12// appended to a bounded queue and refused with ErrStreamBackpressure once the
13// bound is reached, so only that stream is settled and every sibling and the
14// physical connection stay untouched. The consumer never blocks the writer:
15// Read serves already-queued chunks or parks on the wake channel, and Close
16// releases both ends promptly. Write hands each byte slice to the injected
17// outbound hook, which owns chunking it under the negotiated stream chunk
18// ceiling and enqueueing the frames on the connection.
19type streamPipe struct {
20	mu       sync.Mutex
21	queue    [][]byte
22	queued   int
23	closed   bool
24	err      error
25	maxChunk int
26	maxBytes int
27
28	wake     chan struct{}
29	closedCh chan struct{}
30
31	outbound func([]byte) error
32}
33
34// newStreamPipe builds one bounded stream carriage. outbound must not be nil.
35func newStreamPipe(maxChunks int, maxBytes uint64, outbound func([]byte) error) *streamPipe {
36	if maxChunks <= 0 {
37		maxChunks = DefaultStreamInboundChunks
38	}
39	if maxBytes == 0 {
40		maxBytes = DefaultStreamInboundBytes
41	}
42	return &streamPipe{
43		maxChunk: maxChunks,
44		maxBytes: int(maxBytes),
45		wake:     make(chan struct{}, 1),
46		closedCh: make(chan struct{}),
47		outbound: outbound,
48	}
49}
50
51// Read serves the next queued chunk, blocking until one arrives or the pipe
52// closes. A partial copy retains the unread remainder for the next call.
53func (p *streamPipe) Read(b []byte) (int, error) {
54	if len(b) == 0 {
55		return 0, nil
56	}
57	for {
58		p.mu.Lock()
59		if len(p.queue) > 0 {
60			chunk := p.queue[0]
61			n := copy(b, chunk)
62			if n == len(chunk) {
63				p.queue[0] = nil
64				p.queue = p.queue[1:]
65			} else {
66				p.queue[0] = chunk[n:]
67			}
68			p.queued -= n
69			p.mu.Unlock()
70			return n, nil
71		}
72		if p.closed {
73			err := p.err
74			p.mu.Unlock()
75			if err != nil {
76				return 0, err
77			}
78			return 0, io.EOF
79		}
80		p.mu.Unlock()
81		select {
82		case <-p.wake:
83		case <-p.closedCh:
84		}
85	}
86}
87
88// Write hands one byte slice to the outbound hook. It returns ErrClosed once the
89// pipe reached its terminal outcome.
90func (p *streamPipe) Write(b []byte) (int, error) {
91	p.mu.Lock()
92	closed := p.closed
93	p.mu.Unlock()
94	if closed {
95		return 0, ErrStreamGone
96	}
97	if err := p.outbound(b); err != nil {
98		return 0, err
99	}
100	return len(b), nil
101}
102
103// deliver accepts one inbound chunk from the connection reader. It returns
104// ErrStreamBackpressure when the bound is reached and ErrStreamGone once the
105// pipe is closed; the caller settles the affected stream in both cases.
106func (p *streamPipe) deliver(chunk []byte) error {
107	if len(chunk) == 0 {
108		return nil
109	}
110	p.mu.Lock()
111	if p.closed {
112		p.mu.Unlock()
113		return ErrStreamGone
114	}
115	if len(p.queue) >= p.maxChunk || p.queued+len(chunk) > p.maxBytes {
116		p.mu.Unlock()
117		return ErrStreamBackpressure
118	}
119	owned := append([]byte(nil), chunk...)
120	p.queue = append(p.queue, owned)
121	p.queued += len(owned)
122	p.mu.Unlock()
123	select {
124	case p.wake <- struct{}{}:
125	default:
126	}
127	return nil
128}
129
130// closeWith settles the pipe exactly once with cause. A nil cause is an orderly
131// close; every blocked Read observes it promptly. On a nil cause, any chunks
132// already queued are kept: Read serves them first and only returns io.EOF once
133// the queue is drained, so a reply that arrived just before an orderly peer
134// close is never discarded underneath its reader. An error cause is a failure:
135// the queue is discarded, since its content can no longer be trusted to reach
136// a consumer that is being torn down for cause.
137func (p *streamPipe) closeWith(cause error) {
138	p.mu.Lock()
139	if p.closed {
140		p.mu.Unlock()
141		return
142	}
143	p.closed = true
144	p.err = cause
145	if cause != nil {
146		p.queue = nil
147		p.queued = 0
148	}
149	p.mu.Unlock()
150	close(p.closedCh)
151	select {
152	case p.wake <- struct{}{}:
153	default:
154	}
155}
156
157// Close settles the pipe as an orderly close.
158func (p *streamPipe) Close() error {
159	p.closeWith(nil)
160	return nil
161}

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