package xturbo import ( "encoding/binary" "errors" ) // ErrRingFull reports more than ringSize registered requests outstanding: // the Expect slot of seq still belongs to an earlier request that has not // completed. Treat it as a bug: Sync, then retry. var ErrRingFull = errors.New("xturbo: reply ring full") var errExpectUnsent = errors.New("xturbo: Expect of a sequence number not yet sent") var errReservedKind = errors.New("xturbo: reply kinds below 16 are reserved") const ( ringSize = 1024 ringMask = ringSize - 1 ) // Reply kinds below kindUser are reserved by root and consumed internally; // they never reach Handler.OnReply. const ( kindNone uint16 = iota // unregistered kindDiscard // consume and drop (resync GetInputFocus) kindInternAtom // InternAtoms: atom into dst kindQueryExtension // connect: fill the ExtTable kindXIDRange // XC-MISC GetXIDRange refill kindBigreqEnable // BIG-REQUESTS Enable: Buf limit kindUser uint16 = 16 ) // slot is one Expect registration; seq 0 means free. type slot struct { seq Seq kind uint16 args [4]uint32 } // Stats are connection counters, for diagnostics. type Stats struct { DroppedReplies uint64 // replies with no Expect slot UnhandledErrors uint64 // errors dispatched with a nil Handler.OnError UnknownEvents uint64 // events decoded as KindUnknown Resyncs uint64 // GetInputFocus appended by Flush to re-sync sequences MaxRingUse int // most Expect slots outstanding at once } // Stats returns the connection counters. func (c *Conn) Stats() Stats { return c.stats } // Expect registers the request seq so its reply, or its error, is delivered // with kind and args a0..a3 (Reply.Kind/Args, XError.Kind/Args). Call it // before the next Dispatch. kind must be 16 or more; lower kinds are reserved. // A reply without a registration is dropped (Stats.DroppedReplies); a // registered request that completes without reply or error frees its slot // silently once a later sequence number arrives. seq 0 (a failed request) and // requests already completed are ignored. A seq that was never sent (at or // beyond Buf.Next) is a caller bug and fails. ErrRingFull: more than 1024 // registered requests outstanding. func (c *Conn) Expect(seq Seq, kind uint16, a0, a1, a2, a3 uint32) error { if kind < kindUser { return errReservedKind } if seq >= c.buf.Next() { return errExpectUnsent } return c.expect(seq, kind, [4]uint32{a0, a1, a2, a3}) } func (c *Conn) expect(seq Seq, kind uint16, args [4]uint32) error { if seq == 0 || seq < c.seq { return nil } s := &c.ring[seq&ringMask] switch s.seq { case seq: case 0: c.live++ c.stats.MaxRingUse = max(c.stats.MaxRingUse, c.live) default: return ErrRingFull } *s = slot{seq: seq, kind: kind, args: args} return nil } // retire frees the slots of requests in [prev, full): a packet with sequence // full proves they completed. The slot of full itself stays, its reply or // error may still follow its events. func (c *Conn) retire(prev, full Seq) { if c.live == 0 || full <= prev { return } if full-prev >= ringSize { for i := range c.ring { if s := c.ring[i].seq; s != 0 && s < full { c.ring[i] = slot{} c.live-- } } return } for seq := max(prev, 1); seq < full; seq++ { if s := &c.ring[seq&ringMask]; s.seq == seq { *s = slot{} c.live-- } } } // take frees and returns the slot of seq, or ok=false when none is registered. func (c *Conn) take(seq Seq) (kind uint16, args [4]uint32, ok bool) { s := &c.ring[seq&ringMask] if s.seq != seq || seq == 0 { return 0, [4]uint32{}, false } kind, args = s.kind, s.args *s = slot{} c.live-- return kind, args, true } // consumeReply handles a reply registered with a reserved kind. kindDiscard // and malformed replies are dropped. func (c *Conn) consumeReply(r *Reply) { if len(r.Raw) < packetHeadSize { return } switch r.Kind { case kindInternAtom: if i := int(r.Args[0]); i < len(c.atoms) { c.atoms[i] = binary.LittleEndian.Uint32(r.Raw[8:]) } case kindQueryExtension: id := ExtID(r.Args[0]) if r.Raw[8] == 0 || id == ExtCore || id >= extCount { return } c.ext.Set(id, r.Raw[9], r.Raw[10], r.Raw[11]) if id == ExtBigRequests && r.Raw[9] != 0 { // Enable needs the major opcode: queue it from here so one // Sync covers the whole bootstrap. if _, seq := c.buf.Request(r.Raw[9], 0, 0); seq != 0 { _ = c.expect(seq, kindBigreqEnable, [4]uint32{}) } } case kindBigreqEnable: // A limit below the core one would be a server bug; keep the core one. if words := binary.LittleEndian.Uint32(r.Raw[8:]); words > coreMaxReq/4 { c.buf.setMaxReqWords(words) } case kindXIDRange: c.gotXIDRange(binary.LittleEndian.Uint32(r.Raw[8:]), binary.LittleEndian.Uint32(r.Raw[12:])) } } // consumeError handles an error whose request was registered with a reserved // kind (1..15) and reports whether it consumed it; an unconsumed error is // delivered to OnError. Every reserved kind is root's own request, so all are // consumed: the first is kept for the blocking call (Sync, InternAtoms) that // issued the request, which returns it as *XError; a GetXIDRange error (maybe // from PrefetchXIDs) is kept for NewXID. func (c *Conn) consumeError(e *XError) bool { if e.Kind == kindXIDRange { // Possibly a prefetch: kept apart from the error of a Sync. c.xidNone, c.xidFailed, c.xidErr = true, true, *e return true } if !c.syncFailed { c.syncFailed = true c.syncErr = *e } return true }