1package xturbo
2
3import (
4 "errors"
5 "time"
6)
7
8// errXIDsExhausted reports that the server has no resource IDs left for this
9// client: XC-MISC GetXIDRange returned an empty range, or one outside this
10// client's base and mask.
11var errXIDsExhausted = errors.New("xturbo: server has no resource IDs left")
12
13const (
14 // xidFreeCap bounds the FreeXID list; IDs freed beyond it are not reused.
15 xidFreeCap = 256
16 // xidLowMark is the remaining-ID count below which XIDsLow reports true.
17 xidLowMark = 4096
18)
19
20// xidRange is a run of count IDs base|next, base|next+inc, … (inc = lowest
21// bit of the mask).
22type xidRange struct {
23 next, left uint32
24}
25
26// initXIDs starts allocation over the setup range: base|0, base|inc, …
27// up to base|mask, in libxcb's order.
28func (c *Conn) initXIDs() {
29 inc := c.xidMask & -c.xidMask
30 if inc == 0 {
31 return
32 }
33 c.xids = xidRange{0, c.xidMask/inc + 1}
34}
35
36// NewXID returns a fresh resource ID for this client. IDs released with
37// FreeXID are reused first, then the current range, then a range fetched
38// ahead by PrefetchXIDs. When all are empty, outside a Handler callback, one
39// blocking XC-MISC GetXIDRange round trip (bounded like Sync) refills it;
40// packets read meanwhile go to the last Handler given to Dispatch. Inside a
41// callback that refill is impossible and NewXID fails instead, so an event
42// loop allocating from callbacks calls PrefetchXIDs whenever XIDsLow reports
43// true (the XWM does it once per loop iteration). It also fails when XC-MISC
44// is absent or the server has no IDs left.
45func (c *Conn) NewXID() (uint32, error) {
46 if c.xidFreeN > 0 {
47 c.xidFreeN--
48 return c.xidFree[c.xidFreeN], nil
49 }
50 if c.xids.left == 0 {
51 if c.xidPend.left == 0 {
52 if err := c.refillXIDs(); err != nil {
53 return 0, err
54 }
55 }
56 c.xids, c.xidPend = c.xidPend, xidRange{}
57 }
58 id := c.xidBase | c.xids.next
59 c.xids.next += c.xidMask & -c.xidMask
60 c.xids.left--
61 return id, nil
62}
63
64// FreeXID gives back id for reuse by a later NewXID. id must come from
65// NewXID on this Conn and be freed once, after the caller destroyed its
66// resource (the destroy request may still be queued, it precedes any reuse
67// on the wire). IDs outside this client's range, 0, and IDs beyond the free
68// list capacity are ignored; freeing an ID twice hands it out twice.
69func (c *Conn) FreeXID(id uint32) {
70 if id&^c.xidMask != c.xidBase || c.xidFreeN == xidFreeCap {
71 return
72 }
73 c.xidFree[c.xidFreeN] = id
74 c.xidFreeN++
75}
76
77// XIDsLow reports that fewer than 4096 IDs remain in the current range with
78// an empty free list, and that PrefetchXIDs would help: XC-MISC is present,
79// no prefetched range is waiting, no prefetch is in flight and the last one
80// did not report the server out of IDs (after that only NewXID's blocking
81// refill retries).
82func (c *Conn) XIDsLow() bool {
83 if c.xidFreeN > 0 || c.xidPend.left > 0 || c.xidNone || c.xidInFlight() || !c.ext.Info(ExtXCMisc).Present {
84 return false
85 }
86 return c.xids.left < xidLowMark
87}
88
89// PrefetchXIDs queues an XC-MISC GetXIDRange without waiting; its reply,
90// read by a later Dispatch, becomes the range NewXID switches to when the
91// current one runs out. Callers flush as usual. It does nothing while a
92// prefetched range is unused or a prefetch is in flight, and may be called
93// from a Handler callback. It fails with ErrExtMissing without XC-MISC,
94// ErrRingFull when the next request's Expect slot is taken, and with the
95// Buf's sticky error.
96func (c *Conn) PrefetchXIDs() error {
97 if c.fd < 0 {
98 return ErrClosed
99 }
100 if !c.ext.Info(ExtXCMisc).Present {
101 return ErrExtMissing
102 }
103 if c.xidPend.left > 0 || c.xidInFlight() {
104 return nil
105 }
106 return c.queueXIDRange()
107}
108
109// queueXIDRange queues a GetXIDRange registered with kindXIDRange.
110func (c *Conn) queueXIDRange() error {
111 if c.ring[c.buf.next&ringMask].seq != 0 {
112 return ErrRingFull
113 }
114 seq := encodeExtRequest(c.buf, ExtXCMisc, minorGetXIDRange)
115 if seq == 0 {
116 return c.buf.Err()
117 }
118 if err := c.expect(seq, kindXIDRange, [4]uint32{}); err != nil {
119 return err
120 }
121 c.xidReq = seq
122 c.xidNone, c.xidFailed = false, false
123 return nil
124}
125
126// xidInFlight reports a GetXIDRange whose reply or error has not arrived.
127func (c *Conn) xidInFlight() bool {
128 return c.xidReq != 0 && c.ring[c.xidReq&ringMask].seq == c.xidReq
129}
130
131// gotXIDRange records a GetXIDRange reply (from consumeReply).
132func (c *Conn) gotXIDRange(start, count uint32) {
133 if !c.validXIDRange(start, count) {
134 c.xidNone = true
135 return
136 }
137 c.xidPend = xidRange{start & c.xidMask, count}
138}
139
140// validXIDRange accepts a GetXIDRange answer only when every ID of it lies in
141// this client's base and mask. The server answers start 0 (count 0 or 1)
142// when it has none.
143func (c *Conn) validXIDRange(start, count uint32) bool {
144 inc := uint64(c.xidMask & -c.xidMask)
145 off := uint64(start & c.xidMask)
146 return inc != 0 && count != 0 && start != 0 &&
147 start&^c.xidMask == c.xidBase && off%inc == 0 &&
148 off+uint64(count-1)*inc <= uint64(c.xidMask)
149}
150
151// refillXIDs blocks until a prefetched range is available: it waits for the
152// prefetch in flight, or queues one.
153func (c *Conn) refillXIDs() error {
154 if c.dispatching {
155 return errReentrantDispatch
156 }
157 if c.fd < 0 {
158 return ErrClosed
159 }
160 if !c.ext.Info(ExtXCMisc).Present {
161 return ErrExtMissing
162 }
163 deadline := time.Now().Add(c.timeout)
164 if !c.xidInFlight() {
165 if err := c.waitSlot(deadline); err != nil {
166 return err
167 }
168 if err := c.queueXIDRange(); err != nil {
169 return err
170 }
171 }
172 if err := c.waitUntil(c.h, deadline); err != nil {
173 return err
174 }
175 switch {
176 case c.xidFailed:
177 e := c.xidErr
178 return &e
179 case c.xidPend.left == 0:
180 return errXIDsExhausted
181 }
182 return nil
183}
184
185// waitSlot makes sure the ring slot of the next request is free, so a
186// reserved registration can always be made for it: when an earlier request
187// still holds it, it waits for every outstanding request (fenced, so even a
188// registered request without reply completes).
189func (c *Conn) waitSlot(deadline time.Time) error {
190 if c.ring[c.buf.next&ringMask].seq == 0 {
191 return nil
192 }
193 return c.waitUntil(c.h, deadline)
194}