broker_stream_window.go RawHistoryFolder

1package ports
2
3import (
4	"sync"
5)
6
7// Logical stream admission window.
8//
9// One connection's logical stream IDs are allocated by its calling service and
10// are strictly increasing, but two concurrent opens may be scheduled in either
11// order: an ID can be abandoned, and a lower ID can legitimately arrive after a
12// higher one was already admitted. A plain "not above the last admitted ID"
13// check would refuse that valid concurrency, so every admission layer (the
14// client, the listener, and the pool) applies one bounded anti-replay window
15// instead.
16
17const (
18	// BrokerStreamWindowSize is the anti-replay window: an identity that trails
19	// the newest admitted identity by BrokerStreamWindowSize or more is stale
20	// and never admitted, while every unconsumed identity inside the window is
21	// admitted exactly once.
22	BrokerStreamWindowSize = 1024
23	// brokerStreamWindowWords is the window's bitset size in 64-bit words.
24	brokerStreamWindowWords = BrokerStreamWindowSize / 64
25)
26
27// BrokerStreamWindow is the bounded anti-replay admission window for the
28// logical stream IDs of exactly one connection scope. Every identity is
29// consumed at most once: a duplicate inside the window, and an identity evicted
30// from it, are both refused as stale. It is safe for concurrent use and
31// performs no I/O.
32//
33// The window is deliberately transport- and codec-independent: the broker IPC
34// client, the broker IPC listener, and the broker core pool share this exact
35// rule, so a request refused by one layer is refused by the next for the same
36// reason rather than by a weaker monotone comparison.
37type BrokerStreamWindow struct {
38	mu      sync.Mutex
39	highest BrokerStreamID
40	// base is the lowest identity the window still represents. It is zero
41	// before the first admission and at least one afterwards.
42	base  BrokerStreamID
43	words [brokerStreamWindowWords]uint64
44}
45
46// Admit consumes one stream identity for this connection. It reports
47// BrokerAdmissionInvalid for a zero identity, and BrokerAdmissionStale for an
48// identity already consumed or evicted from the window because it trails the
49// newest admitted identity by BrokerStreamWindowSize or more. An admitted
50// identity stays consumed even when the caller refuses the open afterwards, so
51// a rejected request can never be replayed with the same identity.
52func (w *BrokerStreamWindow) Admit(stream BrokerStreamID) error {
53	if w == nil {
54		return BrokerAdmissionInvalid
55	}
56	if stream == 0 {
57		return BrokerAdmissionInvalid
58	}
59	w.mu.Lock()
60	defer w.mu.Unlock()
61	if w.highest == 0 {
62		// The first admission opens a window anchored at one, so a lower
63		// identity that a concurrent open legitimately allocated earlier can
64		// still arrive afterwards.
65		w.highest = stream
66		w.base = windowBase(stream)
67		w.setLocked(stream - w.base)
68		return nil
69	}
70	if stream < w.base {
71		return BrokerAdmissionStale
72	}
73	if stream <= w.highest {
74		offset := stream - w.base
75		if w.words[offset/64]&(1<<(offset%64)) != 0 {
76			return BrokerAdmissionStale
77		}
78		w.setLocked(offset)
79		return nil
80	}
81	// The incoming identity is the newest: slide the window forward. Only then
82	// can an identity leave the window, which is what makes a very late open
83	// stale instead of merely duplicate.
84	next := windowBase(stream)
85	if next > w.base {
86		w.shiftLocked(next - w.base)
87		w.base = next
88	}
89	w.highest = stream
90	w.setLocked(stream - w.base)
91	return nil
92}
93
94// windowBase returns the lowest identity a window whose newest admitted
95// identity is highest still represents.
96func windowBase(highest BrokerStreamID) BrokerStreamID {
97	if highest <= BrokerStreamWindowSize {
98		return 1
99	}
100	return highest - (BrokerStreamWindowSize - 1)
101}
102
103// setLocked marks one window offset as consumed.
104func (w *BrokerStreamWindow) setLocked(offset BrokerStreamID) {
105	w.words[offset/64] |= 1 << (offset % 64)
106}
107
108// shiftLocked drops the oldest shift identities from the window. A shift at or
109// above the window size clears it entirely.
110func (w *BrokerStreamWindow) shiftLocked(shift BrokerStreamID) {
111	if shift >= BrokerStreamWindowSize {
112		w.words = [brokerStreamWindowWords]uint64{}
113		return
114	}
115	words := int(shift / 64)
116	bits := uint(shift % 64)
117	var shifted [brokerStreamWindowWords]uint64
118	for i := range shifted {
119		index := i + words
120		if index >= brokerStreamWindowWords {
121			break
122		}
123		// Go defines a shift at or above the operand width as zero, so a zero
124		// bit shift needs no special case.
125		var high uint64
126		if index+1 < brokerStreamWindowWords {
127			high = w.words[index+1] << (64 - bits)
128		}
129		shifted[i] = w.words[index]>>bits | high
130	}
131	w.words = shifted
132}

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