v0.3.0 2df0761

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

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