mirror of
https://github.com/mochi-mqtt/server.git
synced 2025-10-07 09:01:26 +08:00
208 lines
5.1 KiB
Go
208 lines
5.1 KiB
Go
package circ
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/mochi-co/mqtt/debug"
|
|
)
|
|
|
|
var (
|
|
// bufferSize is the default size of the buffer.
|
|
bufferSize int64 = 1024 * 256
|
|
|
|
// blockSize is the default size of bytes per R/W block.
|
|
blockSize int64 = 2048
|
|
)
|
|
|
|
// buffer contains core values and methods to be included in a reader or writer.
|
|
type buffer struct {
|
|
sync.RWMutex
|
|
|
|
// id is the identifier of the buffer. This is used in debug output.
|
|
id string
|
|
|
|
// size is the size of the buffer.
|
|
size int64
|
|
|
|
// block is the size of the R/W block.
|
|
block int64
|
|
|
|
// buffer is the circular byte buffer.
|
|
buf []byte
|
|
|
|
// tmp is a temporary buffer.
|
|
tmp []byte
|
|
|
|
// head is the current position in the sequence.
|
|
head int64
|
|
|
|
// tail is the committed position in the sequence, I.E. where we
|
|
// have successfully consumed and processed to.
|
|
tail int64
|
|
|
|
// rcond is the sync condition for the reader.
|
|
rcond *sync.Cond
|
|
|
|
// done indicates that the buffer is closed.
|
|
done int64
|
|
|
|
// wcond is the sync condition for the writer.
|
|
wcond *sync.Cond
|
|
}
|
|
|
|
// newBuffer returns a new instance of buffer.
|
|
func newBuffer(size, block int64) buffer {
|
|
|
|
if size == 0 {
|
|
size = bufferSize
|
|
}
|
|
|
|
if block == 0 {
|
|
block = blockSize
|
|
}
|
|
if size < 2*block {
|
|
size = 2 * block
|
|
}
|
|
|
|
return buffer{
|
|
size: size,
|
|
block: block,
|
|
buf: make([]byte, size),
|
|
tmp: make([]byte, size),
|
|
rcond: sync.NewCond(new(sync.Mutex)),
|
|
wcond: sync.NewCond(new(sync.Mutex)),
|
|
}
|
|
}
|
|
|
|
// Close notifies the buffer event loops to stop.
|
|
func (b *buffer) Close() {
|
|
atomic.StoreInt64(&b.done, 1)
|
|
debug.Println(b.id, "[B] STORE done=1 buffer closing")
|
|
|
|
b.wcond.L.Lock()
|
|
b.wcond.Broadcast()
|
|
b.wcond.L.Unlock()
|
|
b.rcond.L.Lock()
|
|
b.rcond.Broadcast()
|
|
b.rcond.L.Unlock()
|
|
|
|
debug.Println(b.id, "[B] DONE REBROADCASTED")
|
|
}
|
|
|
|
// Get will return the tail and head positions of the buffer.
|
|
func (b *buffer) GetPos() (int64, int64) {
|
|
return atomic.LoadInt64(&b.tail), atomic.LoadInt64(&b.head)
|
|
}
|
|
|
|
// SetPos sets the head and tail of the buffer. This method should only be
|
|
// used for testing.
|
|
func (b *buffer) SetPos(tail, head int64) {
|
|
b.tail, b.head = tail, head
|
|
}
|
|
|
|
// Set writes bytes to a byte buffer. This method should only be used for testing
|
|
// and will panic if out of range.
|
|
func (b *buffer) Set(p []byte, start, end int) {
|
|
o := 0
|
|
for i := start; i < end; i++ {
|
|
b.buf[i] = p[o]
|
|
o++
|
|
}
|
|
}
|
|
|
|
// awaitCapacity will hold until there are n bytes free in the buffer, blocking
|
|
// until there are at least n bytes to write without overrunning the tail.
|
|
func (b *buffer) awaitCapacity(n int64) (head int64, err error) {
|
|
head = atomic.LoadInt64(&b.head)
|
|
next := head + n
|
|
wrapped := next - b.size
|
|
tail := atomic.LoadInt64(&b.tail)
|
|
|
|
debug.Println(b.id, "[B] awaiting capacity (n)", n)
|
|
b.rcond.L.Lock()
|
|
for ; wrapped > tail || (tail > head && next > tail && wrapped < 0); tail = atomic.LoadInt64(&b.tail) {
|
|
debug.Println(b.id, "[B] iter no capacity")
|
|
|
|
//fmt.Println("\t", wrapped, ">", tail, wrapped > tail, "||", tail, ">", head, "&&", next, ">", tail, "&&", wrapped, "<", 0, (tail > head && next > tail && wrapped < 0))
|
|
if atomic.LoadInt64(&b.done) == 1 {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
debug.Println(b.id, "[B] iter no capacity waiting")
|
|
b.rcond.Wait()
|
|
}
|
|
b.rcond.L.Unlock()
|
|
debug.Println(b.id, "[B] capacity unlocked (tail)", tail)
|
|
|
|
return
|
|
}
|
|
|
|
// awaitFilled will hold until there are at least n bytes waiting between the
|
|
// tail and head.
|
|
func (b *buffer) awaitFilled(n int64) (tail int64, err error) {
|
|
head := atomic.LoadInt64(&b.head)
|
|
tail = atomic.LoadInt64(&b.tail)
|
|
|
|
debug.Println(b.id, "[B] awaiting filled (tail, head)", tail, head)
|
|
b.wcond.L.Lock()
|
|
for ; head > tail && tail+n > head || head < tail && b.size-tail+head < n; head = atomic.LoadInt64(&b.head) {
|
|
debug.Println(b.id, "[B] iter no fill")
|
|
|
|
if atomic.LoadInt64(&b.done) == 1 {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
debug.Println(b.id, "[B] iter no fill waiting")
|
|
b.wcond.Wait()
|
|
}
|
|
b.wcond.L.Unlock()
|
|
debug.Println(b.id, "[B] filled (head)", head)
|
|
|
|
return
|
|
}
|
|
|
|
// CommitHead moves the head position of the buffer n bytes. If there is not enough
|
|
// capacity, the method will wait until there is.
|
|
//func (b *buffer) CommitHead(n int64) error {
|
|
// return nil
|
|
//}
|
|
|
|
// CommitTail moves the tail position of the buffer n bytes, and will wait until
|
|
// there is enough capacity for at least n bytes.
|
|
func (b *buffer) CommitTail(n int64) error {
|
|
debug.Println(b.id, "[B] commit/discard (n)", n)
|
|
_, err := b.awaitFilled(n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
debug.Println(b.id, "[B] commit/discard unlocked")
|
|
|
|
tail := atomic.LoadInt64(&b.tail)
|
|
if tail+n < b.size {
|
|
atomic.StoreInt64(&b.tail, tail+n)
|
|
debug.Println(b.id, "[B] STORE TAIL", tail+n)
|
|
} else {
|
|
atomic.StoreInt64(&b.tail, (tail+n)%b.size)
|
|
debug.Println(b.id, "[B] STORE TAIL (wrapped)", (tail+n)%b.size)
|
|
}
|
|
|
|
b.rcond.L.Lock()
|
|
b.rcond.Broadcast()
|
|
b.rcond.L.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// capDelta returns the difference between the head and tail.
|
|
func (b *buffer) capDelta(tail, head int64) int64 {
|
|
if head < tail {
|
|
debug.Println(b.id, "[B] capdelta (tail, head, v)", tail, head, b.size-tail+head)
|
|
return b.size - tail + head
|
|
}
|
|
|
|
debug.Println(b.id, "[B] capdelta (tail, head, v)", tail, head, head-tail)
|
|
return head - tail
|
|
}
|