main 7c2aaeb

queue.go RawHistoryFolder

1package neferclient
2
3import (
4	"sync"
5
6	"golang.org/x/sys/unix"
7)
8
9const (
10	ringSize   = 256 // events queued between the reader and Dispatch
11	maxNameLen = 64  // longest output name kept; longer names are truncated
12)
13
14type eventKind uint8
15
16const (
17	evGlobal       eventKind = iota + 1 // registry global (wl_output only): global, version
18	evGlobalRemove                      // registry global_remove: global
19	evOutputName                        // name, global
20	evOutputScale                       // a = factor, global
21	evOutputMode                        // flags, a = width, b = height, global
22	evOutputDone                        // global
23	evFDReady                           // fd (epoll data)
24
25	// Surface events. id is the wl_surface object id (SurfaceID).
26	evPing               // serial (xdg_wm_base)
27	evXdgToplevel        // id, a = width, b = height (before the matching evXdgConfigure)
28	evXdgConfigure       // id, serial
29	evLayerConfigure     // id, serial, a = width, b = height
30	evLockConfigure      // id, serial, a = width, b = height
31	evClosed             // id
32	evFrame              // id
33	evPreferredScale     // id, a = fractional scale in 120ths
34	evBufferScale        // id, a = integer factor
35	evFeedbackTable      // id, fd = owned descriptor, flags = size
36	evFeedbackMainDevice // id, dev
37	evFeedbackFormats    // id, name = one chunk of tranche format indices
38	evFeedbackDone       // id
39	evLocked             // id = lock generation
40	evLockFinished       // id = lock generation
41	evPopupConfigure     // id, a = width, b = height (before the matching evXdgConfigure)
42	evPopupDone          // id
43	evBadEvent           // id, a = badEvent code: the reader met a malformed event
44
45	// Seat events (kind >= evSeatCaps). Pointer and keyboard events carry the
46	// generation of the wl_pointer/wl_keyboard in version, so events of a
47	// released device that are still queued are ignored. global is a wl_surface
48	// object id where a surface is named.
49	evSeatCaps     // a = capabilities
50	evSeatBad      // a = bad code: a seat device event was malformed
51	evPtrEnter     // global = surface, serial, a, b = fixed x, y
52	evPtrLeave     // global = surface
53	evPtrMotion    // a, b = fixed x, y
54	evPtrButton    // flags = button, a = state
55	evPtrAxis      // flags = axis, a = fixed value
56	evPtrAxisValue // flags = axis, a = value120
57	evPtrAxisStop  // flags = axis
58	evKbKeymap     // fd = owned descriptor, flags = size, a = format
59	evKbEnter      // global = surface, serial
60	evKbLeave      // global = surface
61	evKbKey        // flags = evdev code, a = state
62	evKbModifiers  // flags = depressed, dev = latched | locked<<32, a = group
63	evKbRepeat     // a = rate, b = delay
64)
65
66// event is the only thing the reader and the epoll goroutine hand to the
67// owner. It holds no pointers except through fixed storage, so queuing one
68// never allocates.
69type event struct {
70	kind    eventKind
71	nameLen uint8
72	global  uint32
73	version uint32
74	flags   uint32
75	id      uint32 // wl_surface object id for surface events
76	serial  uint32
77	fd      int32 // epoll data, or a received descriptor owned by the event
78	a, b    int32
79	dev     uint64
80	name    [maxNameLen]byte
81}
82
83// hasFD reports whether the event owns a received descriptor that must be
84// closed if the event is dropped.
85func (e *event) hasFD() bool { return (e.kind == evFeedbackTable || e.kind == evKbKeymap) && e.fd >= 0 }
86
87func (e *event) closeFD() {
88	if e.hasFD() {
89		_ = unix.Close(int(e.fd))
90		e.fd = -1
91	}
92}
93
94func (e *event) setBytes(b []byte) {
95	e.nameLen = uint8(copy(e.name[:], b))
96}
97
98func (e *event) setName(s string) {
99	e.nameLen = uint8(copy(e.name[:], s))
100}
101
102func (e *event) nameBytes() []byte { return e.name[:e.nameLen] }
103
104// queue is the bounded hand-off between producers (the reader goroutine and
105// the epoll goroutine) and the owner goroutine, plus the reader pause gate.
106//
107// A producer facing a full ring waits on cond; Dispatch releases it as it
108// drains. A pause request also releases it: while pausing, producers park
109// events in held instead of waiting, so neither side can block on the other.
110// Parked events are re-posted, in order, by the reader once it resumes.
111type queue struct {
112	mu   sync.Mutex
113	cond sync.Cond // L = &mu; one cond for every state change, all are rare
114
115	ring [ringSize]event
116	head int
117	n    int
118
119	pausing      bool
120	readerParked bool
121	readerExit   bool
122	stopped      bool
123	held         []event
124	spare        []event // capacity reused by the next held generation
125	fatal        error
126
127	wake chan struct{}
128}
129
130func newQueue(pausing bool) *queue {
131	q := &queue{pausing: pausing, wake: make(chan struct{}, 1)}
132	q.cond.L = &q.mu
133	return q
134}
135
136func (q *queue) signal() {
137	select {
138	case q.wake <- struct{}{}:
139	default:
140	}
141}
142
143// post queues ev. It blocks while the ring is full and not pausing.
144func (q *queue) post(ev *event) {
145	q.mu.Lock()
146	for {
147		switch {
148		case q.stopped:
149			q.mu.Unlock()
150			ev.closeFD()
151			return
152		case q.pausing:
153			q.held = append(q.held, *ev)
154			q.mu.Unlock()
155			return
156		case q.n < ringSize:
157			q.ring[(q.head+q.n)%ringSize] = *ev
158			q.n++
159			q.mu.Unlock()
160			q.signal()
161			return
162		}
163		q.cond.Wait()
164	}
165}
166
167// pop moves the oldest queued event into ev.
168func (q *queue) pop(ev *event) bool {
169	q.mu.Lock()
170	if q.n == 0 {
171		q.mu.Unlock()
172		return false
173	}
174	*ev = q.ring[q.head]
175	q.ring[q.head] = event{} // ownership of a carried descriptor moved to ev; leave no key data behind
176	q.head = (q.head + 1) % ringSize
177	full := q.n == ringSize
178	q.n--
179	if full {
180		q.cond.Broadcast()
181	}
182	q.mu.Unlock()
183	return true
184}
185
186// setFatal records the first transport failure; Dispatch returns it once the
187// events queued before it are drained.
188func (q *queue) setFatal(err error) {
189	q.mu.Lock()
190	if q.fatal == nil && !q.stopped {
191		q.fatal = err
192	}
193	q.mu.Unlock()
194	q.signal()
195}
196
197func (q *queue) fatalErr() error {
198	q.mu.Lock()
199	err := q.fatal
200	q.mu.Unlock()
201	return err
202}
203
204func (q *queue) isStopped() bool {
205	q.mu.Lock()
206	s := q.stopped
207	q.mu.Unlock()
208	return s
209}
210
211func (q *queue) stop() {
212	q.mu.Lock()
213	q.stopped = true
214	q.cond.Broadcast()
215	q.mu.Unlock()
216}
217
218// setPausing starts or ends a pause.
219func (q *queue) setPausing(v bool) {
220	q.mu.Lock()
221	q.pausing = v
222	q.cond.Broadcast()
223	q.mu.Unlock()
224}
225
226// endPause ends a pause started by setPausing(true). Events producers parked
227// meanwhile are moved into the ring, in order, as far as it has room, so that
228// everything handled during the pause is visible to the next Dispatch. The
229// remainder stays at the front of held; the reader re-posts it when it resumes.
230func (q *queue) endPause() {
231	q.mu.Lock()
232	m := min(len(q.held), ringSize-q.n)
233	for i := range m {
234		q.ring[(q.head+q.n)%ringSize] = q.held[i]
235		q.n++
236	}
237	if m > 0 {
238		q.held = q.held[:copy(q.held, q.held[m:])]
239	}
240	q.pausing = false
241	q.cond.Broadcast()
242	q.mu.Unlock()
243	if m > 0 {
244		q.signal()
245	}
246}
247
248// awaitParked blocks until the reader is parked or gone.
249func (q *queue) awaitParked() {
250	q.mu.Lock()
251	for !q.readerParked && !q.readerExit {
252		q.cond.Wait()
253	}
254	q.mu.Unlock()
255}
256
257// gate runs on the reader goroutine before every read. It parks the reader
258// while a pause is requested and re-posts what producers parked meanwhile.
259// It reports false when the connection is closing.
260func (q *queue) gate() bool {
261	q.mu.Lock()
262	for q.pausing && !q.stopped {
263		if !q.readerParked {
264			q.readerParked = true
265			q.cond.Broadcast()
266		}
267		q.cond.Wait()
268	}
269	wasParked := q.readerParked
270	q.readerParked = false
271	stopped := q.stopped
272	q.mu.Unlock()
273	if stopped {
274		return false
275	}
276	if wasParked {
277		q.flushHeld()
278	}
279	return true
280}
281
282func (q *queue) flushHeld() {
283	q.mu.Lock()
284	held := q.held
285	q.held = q.spare[:0]
286	q.mu.Unlock()
287	for i := range held {
288		q.post(&held[i])
289	}
290	q.mu.Lock()
291	q.spare = held[:0]
292	q.mu.Unlock()
293}
294
295// takeHeld returns and clears the parked events. Only valid while no reader
296// runs (during setup).
297func (q *queue) takeHeld() []event {
298	q.mu.Lock()
299	held := q.held
300	q.held = nil
301	q.mu.Unlock()
302	return held
303}
304
305// hasLocked reports whether an evLocked for lock generation gen is queued or
306// parked.
307func (q *queue) hasLocked(gen uint32) bool {
308	q.mu.Lock()
309	defer q.mu.Unlock()
310	for i := range q.n {
311		if e := &q.ring[(q.head+i)%ringSize]; e.kind == evLocked && e.id == gen {
312			return true
313		}
314	}
315	for i := range q.held {
316		if q.held[i].kind == evLocked && q.held[i].id == gen {
317			return true
318		}
319	}
320	return false
321}
322
323// closeFDs closes the descriptors owned by events nobody will drain. Only
324// valid once both producers have stopped.
325func (q *queue) closeFDs() {
326	q.mu.Lock()
327	for i := range q.n {
328		q.ring[(q.head+i)%ringSize].closeFD()
329	}
330	for i := range q.held {
331		q.held[i].closeFD()
332	}
333	q.mu.Unlock()
334}
335
336// length reports how many events are queued.
337func (q *queue) length() int {
338	q.mu.Lock()
339	n := q.n
340	q.mu.Unlock()
341	return n
342}
343
344// pending reports whether events are queued.
345func (q *queue) pending() bool {
346	q.mu.Lock()
347	n := q.n
348	q.mu.Unlock()
349	return n > 0
350}
351
352func (q *queue) readerDone() {
353	q.mu.Lock()
354	q.readerExit = true
355	q.cond.Broadcast()
356	q.mu.Unlock()
357}

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 1

Tags 4

↑↓ moveenter openesc close