package xturbo import ( "errors" "time" ) // errXIDsExhausted reports that the server has no resource IDs left for this // client: XC-MISC GetXIDRange returned an empty range, or one outside this // client's base and mask. var errXIDsExhausted = errors.New("xturbo: server has no resource IDs left") const ( // xidFreeCap bounds the FreeXID list; IDs freed beyond it are not reused. xidFreeCap = 256 // xidLowMark is the remaining-ID count below which XIDsLow reports true. xidLowMark = 4096 ) // xidRange is a run of count IDs base|next, base|next+inc, … (inc = lowest // bit of the mask). type xidRange struct { next, left uint32 } // initXIDs starts allocation over the setup range: base|0, base|inc, … // up to base|mask, in libxcb's order. func (c *Conn) initXIDs() { inc := c.xidMask & -c.xidMask if inc == 0 { return } c.xids = xidRange{0, c.xidMask/inc + 1} } // NewXID returns a fresh resource ID for this client. IDs released with // FreeXID are reused first, then the current range, then a range fetched // ahead by PrefetchXIDs. When all are empty, outside a Handler callback, one // blocking XC-MISC GetXIDRange round trip (bounded like Sync) refills it; // packets read meanwhile go to the last Handler given to Dispatch. Inside a // callback that refill is impossible and NewXID fails instead, so an event // loop allocating from callbacks calls PrefetchXIDs whenever XIDsLow reports // true (the XWM does it once per loop iteration). It also fails when XC-MISC // is absent or the server has no IDs left. func (c *Conn) NewXID() (uint32, error) { if c.xidFreeN > 0 { c.xidFreeN-- return c.xidFree[c.xidFreeN], nil } if c.xids.left == 0 { if c.xidPend.left == 0 { if err := c.refillXIDs(); err != nil { return 0, err } } c.xids, c.xidPend = c.xidPend, xidRange{} } id := c.xidBase | c.xids.next c.xids.next += c.xidMask & -c.xidMask c.xids.left-- return id, nil } // FreeXID gives back id for reuse by a later NewXID. id must come from // NewXID on this Conn and be freed once, after the caller destroyed its // resource (the destroy request may still be queued, it precedes any reuse // on the wire). IDs outside this client's range, 0, and IDs beyond the free // list capacity are ignored; freeing an ID twice hands it out twice. func (c *Conn) FreeXID(id uint32) { if id&^c.xidMask != c.xidBase || c.xidFreeN == xidFreeCap { return } c.xidFree[c.xidFreeN] = id c.xidFreeN++ } // XIDsLow reports that fewer than 4096 IDs remain in the current range with // an empty free list, and that PrefetchXIDs would help: XC-MISC is present, // no prefetched range is waiting, no prefetch is in flight and the last one // did not report the server out of IDs (after that only NewXID's blocking // refill retries). func (c *Conn) XIDsLow() bool { if c.xidFreeN > 0 || c.xidPend.left > 0 || c.xidNone || c.xidInFlight() || !c.ext.Info(ExtXCMisc).Present { return false } return c.xids.left < xidLowMark } // PrefetchXIDs queues an XC-MISC GetXIDRange without waiting; its reply, // read by a later Dispatch, becomes the range NewXID switches to when the // current one runs out. Callers flush as usual. It does nothing while a // prefetched range is unused or a prefetch is in flight, and may be called // from a Handler callback. It fails with ErrExtMissing without XC-MISC, // ErrRingFull when the next request's Expect slot is taken, and with the // Buf's sticky error. func (c *Conn) PrefetchXIDs() error { if c.fd < 0 { return ErrClosed } if !c.ext.Info(ExtXCMisc).Present { return ErrExtMissing } if c.xidPend.left > 0 || c.xidInFlight() { return nil } return c.queueXIDRange() } // queueXIDRange queues a GetXIDRange registered with kindXIDRange. func (c *Conn) queueXIDRange() error { if c.ring[c.buf.next&ringMask].seq != 0 { return ErrRingFull } seq := encodeExtRequest(c.buf, ExtXCMisc, minorGetXIDRange) if seq == 0 { return c.buf.Err() } if err := c.expect(seq, kindXIDRange, [4]uint32{}); err != nil { return err } c.xidReq = seq c.xidNone, c.xidFailed = false, false return nil } // xidInFlight reports a GetXIDRange whose reply or error has not arrived. func (c *Conn) xidInFlight() bool { return c.xidReq != 0 && c.ring[c.xidReq&ringMask].seq == c.xidReq } // gotXIDRange records a GetXIDRange reply (from consumeReply). func (c *Conn) gotXIDRange(start, count uint32) { if !c.validXIDRange(start, count) { c.xidNone = true return } c.xidPend = xidRange{start & c.xidMask, count} } // validXIDRange accepts a GetXIDRange answer only when every ID of it lies in // this client's base and mask. The server answers start 0 (count 0 or 1) // when it has none. func (c *Conn) validXIDRange(start, count uint32) bool { inc := uint64(c.xidMask & -c.xidMask) off := uint64(start & c.xidMask) return inc != 0 && count != 0 && start != 0 && start&^c.xidMask == c.xidBase && off%inc == 0 && off+uint64(count-1)*inc <= uint64(c.xidMask) } // refillXIDs blocks until a prefetched range is available: it waits for the // prefetch in flight, or queues one. func (c *Conn) refillXIDs() error { if c.dispatching { return errReentrantDispatch } if c.fd < 0 { return ErrClosed } if !c.ext.Info(ExtXCMisc).Present { return ErrExtMissing } deadline := time.Now().Add(c.timeout) if !c.xidInFlight() { if err := c.waitSlot(deadline); err != nil { return err } if err := c.queueXIDRange(); err != nil { return err } } if err := c.waitUntil(c.h, deadline); err != nil { return err } switch { case c.xidFailed: e := c.xidErr return &e case c.xidPend.left == 0: return errXIDsExhausted } return nil } // waitSlot makes sure the ring slot of the next request is free, so a // reserved registration can always be made for it: when an earlier request // still holds it, it waits for every outstanding request (fenced, so even a // registered request without reply completes). func (c *Conn) waitSlot(deadline time.Time) error { if c.ring[c.buf.next&ringMask].seq == 0 { return nil } return c.waitUntil(c.h, deadline) }