v0.1.0 602baa7

sync.go RawHistoryFolder

1package xturbo
2
3import (
4	"encoding/binary"
5	"fmt"
6	"os"
7	"time"
8
9	"golang.org/x/sys/unix"
10)
11
12// Core and extension requests root issues itself. Root does not import the
13// generated packages, so these few are encoded here; tests compare them with
14// the generated encoders and the request goldens.
15const (
16	opInternAtom     = 16
17	opGetInputFocus  = 43
18	opQueryExtension = 98
19	minorGetXIDRange = 1 // XC-MISC
20)
21
22func encodeGetInputFocus(b *Buf) Seq {
23	_, seq := b.Request(opGetInputFocus, 0, 0)
24	return seq
25}
26
27// encodeNamed encodes QueryExtension and InternAtom: CARD16 name_len, 2
28// pad bytes, then the name padded to 4.
29func encodeNamed(b *Buf, major, data uint8, name string) Seq {
30	if len(name) > 0xffff {
31		b.Fail(ErrBadLength)
32		return 0
33	}
34	p, seq := b.Request(major, data, pad4(4+len(name)))
35	if p == nil {
36		return 0
37	}
38	binary.LittleEndian.PutUint16(p, uint16(len(name)))
39	copy(p[4:], name)
40	return seq
41}
42
43func encodeQueryExtension(b *Buf, name string) Seq {
44	return encodeNamed(b, opQueryExtension, 0, name)
45}
46
47func encodeInternAtom(b *Buf, onlyIfExists bool, name string) Seq {
48	var data uint8
49	if onlyIfExists {
50		data = 1
51	}
52	return encodeNamed(b, opInternAtom, data, name)
53}
54
55// encodeExtRequest encodes a body-less extension request (BIG-REQUESTS
56// Enable, XC-MISC GetXIDRange). It fails with ErrExtMissing when id is absent.
57func encodeExtRequest(b *Buf, id ExtID, minor uint8) Seq {
58	major := b.Ext.Major(id)
59	if major == 0 {
60		b.Fail(ErrExtMissing)
61		return 0
62	}
63	_, seq := b.Request(major, minor, 0)
64	return seq
65}
66
67// Bounds of the blocking calls. Vars so tests can shorten them.
68var (
69	// connectTimeout bounds NewConn: setup handshake plus extension bootstrap.
70	connectTimeout = 10 * time.Second
71	// syncTimeout bounds one Sync, InternAtoms or blocking XID refill.
72	syncTimeout = 5 * time.Second
73)
74
75// deadlineError wraps os.ErrDeadlineExceeded for a blocking call that timed
76// out waiting for the server.
77func deadlineError(what string) error {
78	return fmt.Errorf("xturbo: %s: no answer from the X server: %w", what, os.ErrDeadlineExceeded)
79}
80
81// bootstrap negotiates every extension root knows (one pipelined
82// QueryExtension each, then BIG-REQUESTS Enable when present) in one Sync,
83// before deadline.
84func (c *Conn) bootstrap(deadline time.Time) error {
85	for id := ExtCore + 1; id < extCount; id++ {
86		seq := encodeQueryExtension(c.buf, ExtXName[id])
87		if seq == 0 {
88			return c.buf.Err()
89		}
90		if err := c.expect(seq, kindQueryExtension, [4]uint32{uint32(id)}); err != nil {
91			return err
92		}
93	}
94	// consumeReply queues bigreq Enable when BIG-REQUESTS is present; the
95	// wait covers it too.
96	return c.sync(nil, deadline)
97}
98
99// Sync flushes every queued request and blocks until the server has
100// processed them and every request registered with Expect has completed. It
101// appends a GetInputFocus as a fence, so even with nothing registered it is a
102// full round trip: errors of earlier requests have been delivered when it
103// returns. Packets read meanwhile go to h as with Dispatch (nil h: the last
104// Handler given to Dispatch, or none). An error on a request root issued
105// itself is returned as *XError. Sync gives up after 5 s with an error
106// wrapping os.ErrDeadlineExceeded (a hung server); the connection is then
107// still usable but its state unknown, treat it as dead. Meant for startup;
108// called from a Handler callback it returns an error at once.
109func (c *Conn) Sync(h *Handler) error {
110	return c.sync(h, time.Now().Add(c.timeout))
111}
112
113func (c *Conn) sync(h *Handler, deadline time.Time) error {
114	if c.dispatching {
115		return errReentrantDispatch
116	}
117	if h == nil {
118		h = c.h
119	}
120	c.syncFailed = false
121	if err := c.waitUntil(h, deadline); err != nil {
122		return err
123	}
124	if c.syncFailed {
125		e := c.syncErr
126		return &e
127	}
128	return nil
129}
130
131// waitUntil fences, flushes and dispatches, one bounded Dispatch at a time,
132// until the fence has been answered and no registered slot remains, or
133// deadline passes. A fence is re-queued whenever callbacks registered
134// requests after the last one.
135func (c *Conn) waitUntil(h *Handler, deadline time.Time) error {
136	if c.fd < 0 {
137		return ErrClosed
138	}
139	var fence Seq
140	for {
141		if fence != 0 && c.seq >= fence && c.live == 0 {
142			return nil
143		}
144		if fence == 0 || fence < c.buf.next-1 {
145			// Requests queued after the fence (or none yet): fence them.
146			if fence = c.fence(); fence == 0 {
147				return c.buf.Err()
148			}
149		}
150		if err := c.pollOnce(h, deadline); err != nil {
151			return err
152		}
153	}
154}
155
156// pollOnce makes one step of progress: flush, dispatch what is readable and,
157// only when Dispatch read nothing at all, block until the socket is readable
158// (or writable while requests remain queued) or deadline passes. Dispatch is
159// bounded per call, so it may return with input left; the caller loops and
160// re-checks. A deadline already past fails even while packets keep coming
161// (a server flooding events but never answering).
162func (c *Conn) pollOnce(h *Handler, deadline time.Time) error {
163	left := time.Until(deadline)
164	if left <= 0 {
165		return deadlineError("blocking round trip")
166	}
167	events := int16(unix.POLLIN)
168	switch err := c.Flush(); err {
169	case nil:
170	case ErrWouldBlock:
171		events |= unix.POLLOUT
172	default:
173		return err
174	}
175	if err := c.Dispatch(h); err != nil {
176		return err
177	}
178	// Packets handled may have queued requests (callbacks, the bootstrap):
179	// loop to flush them, and to fence them, before blocking.
180	if !c.rdIdle || (events&unix.POLLOUT == 0 && c.buf.Len() > 0) {
181		return nil
182	}
183	return pollFd(c.fd, events, left)
184}
185
186// pollFd blocks until fd has one of events or timeout passes (returning nil;
187// callers re-check their deadline). EINTR is a wake-up.
188func pollFd(fd int, events int16, timeout time.Duration) error {
189	ms := int((timeout + time.Millisecond - 1) / time.Millisecond)
190	fds := []unix.PollFd{{Fd: int32(fd), Events: events}}
191	if _, err := unix.Poll(fds, ms); err != nil && err != unix.EINTR {
192		return os.NewSyscallError("poll", err)
193	}
194	return nil
195}

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 1

↑↓ moveenter openesc close