@@ -17,9 +17,11 @@ const defaultRingBufferCapacity = 256
1717// assembled packet data. It sits between manageReader (producer, calls
1818// Enqueue) and the application goroutine (consumer, calls Dequeue/Done).
1919//
20- // Slots are pre-allocated and reused: each slot's backing array grows via
21- // append to fit incoming data, then stays at its high-water mark, avoiding
22- // per-message allocation in steady state.
20+ // Buffers are obtained from a shared BufferPool. Enqueue copies data into a
21+ // pooled buffer; Dequeue returns that buffer's data and advances the tail
22+ // immediately, and Done releases the buffer back to the pool. Keeping the
23+ // pool behind Dequeue/Done means the consumer does not need to know whether
24+ // the queue is backed by a pool or by fixed buffers.
2325//
2426// After Close, Dequeue drains any queued messages before returning the close
2527// error. This ensures graceful shutdown (KindClose/KindCloseSend) delivers
@@ -28,43 +30,53 @@ type ringBuffer struct {
2830 mu sync.Mutex
2931 cond sync.Cond
3032
31- buf [][]byte // ring of byte slices
32- head int // next write position (producer)
33- tail int // next read position (consumer)
34- count int // number of occupied slots
35-
36- held bool // true between Dequeue and Done
37- err error // terminal error, set by Close
33+ // pool is shared across all streams on a connection and is owned by the
34+ // Manager, not the ring buffer. Its lifetime outlives this buffer, so a
35+ // consumer may safely return a buffer via Done even after Close.
36+ pool * BufferPool
37+ buf []* []byte // ring of pooled buffer pointers
38+ head int // next write position (producer)
39+ tail int // next read position (consumer)
40+ count int // number of occupied slots
41+
42+ held * []byte // buffer from the last Dequeue, released by Done
43+ err error // terminal error, set by Close
3844}
3945
40- func (rb * ringBuffer ) init () {
46+ func (rb * ringBuffer ) init (pool * BufferPool ) {
4147 rb .cond .L = & rb .mu
42- rb .buf = make ([][]byte , defaultRingBufferCapacity )
48+ rb .pool = pool
49+ rb .buf = make ([]* []byte , defaultRingBufferCapacity )
4350}
4451
45- // Enqueue copies data into the next write slot. If the buffer is full, it
46- // blocks until a slot is freed or the buffer is closed. If the buffer is
47- // closed, Enqueue returns silently without enqueuing .
52+ // Enqueue copies data into a pooled buffer and places it in the next write
53+ // slot. If the buffer is full, it blocks until a slot is freed or the buffer
54+ // is closed. If the buffer is closed , Enqueue returns silently.
4855func (rb * ringBuffer ) Enqueue (data []byte ) {
56+ b := rb .pool .Get ()
57+ * b = append (* b , data ... )
58+
4959 rb .mu .Lock ()
5060 defer rb .mu .Unlock ()
5161
5262 for rb .count == len (rb .buf ) && rb .err == nil {
5363 rb .cond .Wait ()
5464 }
5565 if rb .err != nil {
66+ rb .pool .Put (b )
5667 return
5768 }
5869
59- rb .buf [rb .head ] = append ( rb . buf [ rb . head ][: 0 ], data ... )
70+ rb .buf [rb .head ] = b
6071 rb .head = (rb .head + 1 ) % len (rb .buf )
6172 rb .count ++
6273 rb .cond .Broadcast ()
6374}
6475
65- // Dequeue returns the data from the next read slot. If the buffer is empty,
66- // it blocks until data is available or the buffer is closed. The returned
67- // slice is valid until Done is called.
76+ // Dequeue returns the data from the next buffered message and advances the
77+ // tail. The returned slice is valid until Done is called, which releases the
78+ // underlying buffer back to the pool. Done must be called exactly once after
79+ // each successful Dequeue.
6880func (rb * ringBuffer ) Dequeue () ([]byte , error ) {
6981 rb .mu .Lock ()
7082 defer rb .mu .Unlock ()
@@ -76,37 +88,31 @@ func (rb *ringBuffer) Dequeue() ([]byte, error) {
7688 return nil , rb .err
7789 }
7890
79- rb .held = true
80- return rb .buf [rb .tail ], nil
81- }
82-
83- // Done advances the read pointer, making the slot available for reuse.
84- // It must be called exactly once after each successful Dequeue.
85- //
86- // TODO(shubham): remove this method once a shared buffer pool is introduced.
87- // With a pool, Dequeue will advance the tail immediately and the caller will
88- // return the buffer to the pool directly.
89- func (rb * ringBuffer ) Done () {
90- rb .mu .Lock ()
91- defer rb .mu .Unlock ()
92-
91+ b := rb .buf [rb .tail ]
92+ rb .buf [rb .tail ] = nil
9393 rb .tail = (rb .tail + 1 ) % len (rb .buf )
9494 rb .count --
95- rb .held = false
95+ rb .held = b
9696 rb .cond .Broadcast ()
97+
98+ return * b , nil
99+ }
100+
101+ // Done releases the buffer from the most recent Dequeue back to the pool,
102+ // invalidating the slice that Dequeue returned. It must be called exactly
103+ // once after each successful Dequeue. Because the queue is single-consumer,
104+ // Done is only ever called from the same goroutine as Dequeue.
105+ func (rb * ringBuffer ) Done () {
106+ rb .pool .Put (rb .held )
107+ rb .held = nil
97108}
98109
99110// Close marks the buffer as closed with the given error. All blocked Enqueue
100- // and Dequeue calls are woken and will return. Close waits for any in-progress
101- // Dequeue/Done pair to complete before setting the error. Subsequent calls are
102- // no-ops.
111+ // and Dequeue calls are woken and will return. Subsequent calls are no-ops.
103112func (rb * ringBuffer ) Close (err error ) {
104113 rb .mu .Lock ()
105114 defer rb .mu .Unlock ()
106115
107- for rb .held {
108- rb .cond .Wait ()
109- }
110116 if rb .err != nil {
111117 return
112118 }
0 commit comments