Various improvements.
* Expand README.md, provide benchmark results. * Add docs, benchmarks for binheap and circ packages. * Add methods Len() and Capacity(). * Change *sync.Cond to sync.Cond. * TryRecv() and TrySend() distinguish empty and closed errors. * Improve test coverage. * Add basic Makefile. * Fix documentation mistakes.
This commit is contained in:
50
mq/lib.go
50
mq/lib.go
@@ -12,7 +12,7 @@
|
||||
// word1, _ := mq.Recv()
|
||||
// word2, _ := mq.Recv()
|
||||
// fmt.Println(word1, word2)
|
||||
// pq.Close()
|
||||
// q.Close()
|
||||
// // Output: hello world
|
||||
//
|
||||
// # Implementation
|
||||
@@ -30,6 +30,7 @@ package mq
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gogs.humancabbage.net/sam/priorityq"
|
||||
"gogs.humancabbage.net/sam/priorityq/circ"
|
||||
)
|
||||
|
||||
@@ -46,9 +47,9 @@ func Make[T any](cap int) Q[T] {
|
||||
high: high,
|
||||
low: low,
|
||||
}
|
||||
s.canRecv = sync.NewCond(&s.mu)
|
||||
s.canSendHigh = sync.NewCond(&s.mu)
|
||||
s.canSendLow = sync.NewCond(&s.mu)
|
||||
s.canRecv = sync.Cond{L: &s.mu}
|
||||
s.canSendHigh = sync.Cond{L: &s.mu}
|
||||
s.canSendLow = sync.Cond{L: &s.mu}
|
||||
return Q[T]{s}
|
||||
}
|
||||
|
||||
@@ -56,9 +57,9 @@ type state[T any] struct {
|
||||
mu sync.Mutex
|
||||
high circ.B[T]
|
||||
low circ.B[T]
|
||||
canSendHigh *sync.Cond
|
||||
canSendLow *sync.Cond
|
||||
canRecv *sync.Cond
|
||||
canSendHigh sync.Cond
|
||||
canSendLow sync.Cond
|
||||
canRecv sync.Cond
|
||||
closed bool
|
||||
}
|
||||
|
||||
@@ -110,51 +111,55 @@ func (s *state[T]) Send(value T) {
|
||||
|
||||
// SendHigh adds an item with high priority, blocking if full.
|
||||
func (s *state[T]) SendHigh(value T) {
|
||||
s.send(value, &s.high, s.canSendHigh)
|
||||
s.send(value, &s.high, &s.canSendHigh)
|
||||
}
|
||||
|
||||
// SendLow adds an item with low buffer, blocking if full.
|
||||
func (s *state[T]) SendLow(value T) {
|
||||
s.send(value, &s.low, s.canSendLow)
|
||||
s.send(value, &s.low, &s.canSendLow)
|
||||
}
|
||||
|
||||
// TryRecv attempts to get an item from the queue, without blocking.
|
||||
//
|
||||
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
|
||||
func (s *state[T]) TryRecv() (value T, ok bool) {
|
||||
// The error indicates whether the attempt succeeded, the queue is empty, or
|
||||
// the queue is closed.
|
||||
func (s *state[T]) TryRecv() (value T, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.high.CanPop() {
|
||||
value = s.high.PopFront()
|
||||
ok = true
|
||||
s.canSendHigh.Broadcast()
|
||||
return
|
||||
}
|
||||
if s.low.CanPop() {
|
||||
value = s.low.PopFront()
|
||||
ok = true
|
||||
s.canSendLow.Broadcast()
|
||||
return
|
||||
}
|
||||
if s.closed {
|
||||
err = priorityq.ErrClosed
|
||||
} else {
|
||||
err = priorityq.ErrEmpty
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TrySend is an alias for TrySendLow.
|
||||
func (s *state[T]) TrySend(value T) bool {
|
||||
func (s *state[T]) TrySend(value T) error {
|
||||
return s.trySend(value, &s.low)
|
||||
}
|
||||
|
||||
// TrySendHigh attempts to add an item with high priority, without blocking.
|
||||
//
|
||||
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
|
||||
func (s *state[T]) TrySendHigh(value T) bool {
|
||||
// Returns an error from the root priorityq package, or nil if successful.
|
||||
func (s *state[T]) TrySendHigh(value T) error {
|
||||
return s.trySend(value, &s.high)
|
||||
}
|
||||
|
||||
// TrySendLow attempts to add an item with low priority, without blocking.
|
||||
//
|
||||
// If the attempt succeeds, the returned bool is true. Otherwise, it is false.
|
||||
func (s *state[T]) TrySendLow(value T) bool {
|
||||
// Returns an error from the root priorityq package, or nil if successful.
|
||||
func (s *state[T]) TrySendLow(value T) error {
|
||||
return s.trySend(value, &s.low)
|
||||
}
|
||||
|
||||
@@ -176,13 +181,16 @@ func (s *state[T]) send(value T, buf *circ.B[T], cond *sync.Cond) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state[T]) trySend(value T, buf *circ.B[T]) bool {
|
||||
func (s *state[T]) trySend(value T, buf *circ.B[T]) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return priorityq.ErrClosed
|
||||
}
|
||||
if !buf.CanPush() {
|
||||
return false
|
||||
return priorityq.ErrFull
|
||||
}
|
||||
buf.PushBack(value)
|
||||
s.canRecv.Broadcast()
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"gogs.humancabbage.net/sam/priorityq"
|
||||
"gogs.humancabbage.net/sam/priorityq/mq"
|
||||
)
|
||||
|
||||
@@ -77,15 +78,15 @@ func TestDoubleClose(t *testing.T) {
|
||||
func TestTrySendRecv(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := mq.Make[int](4)
|
||||
assumeSendOk := func(n int, f func(int) bool) {
|
||||
ok := f(n)
|
||||
if !ok {
|
||||
assumeSendOk := func(n int, f func(int) error) {
|
||||
err := f(n)
|
||||
if err != nil {
|
||||
t.Errorf("expected to be able to send")
|
||||
}
|
||||
}
|
||||
assumeRecvOk := func(expected int) {
|
||||
actual, ok := q.TryRecv()
|
||||
if !ok {
|
||||
actual, err := q.TryRecv()
|
||||
if err != nil {
|
||||
t.Errorf("expected to be able to receive")
|
||||
}
|
||||
if actual != expected {
|
||||
@@ -94,10 +95,10 @@ func TestTrySendRecv(t *testing.T) {
|
||||
}
|
||||
assumeSendOk(1, q.TrySendLow)
|
||||
assumeSendOk(2, q.TrySendLow)
|
||||
assumeSendOk(3, q.TrySendLow)
|
||||
assumeSendOk(3, q.TrySend)
|
||||
assumeSendOk(4, q.TrySendLow)
|
||||
ok := q.TrySendLow(5)
|
||||
if ok {
|
||||
err := q.TrySendLow(5)
|
||||
if err == nil {
|
||||
t.Errorf("expected low buffer to be full")
|
||||
}
|
||||
assumeRecvOk(1)
|
||||
@@ -109,8 +110,8 @@ func TestTrySendRecv(t *testing.T) {
|
||||
assumeSendOk(6, q.TrySendHigh)
|
||||
assumeSendOk(7, q.TrySendHigh)
|
||||
assumeSendOk(8, q.TrySendHigh)
|
||||
ok = q.TrySendHigh(5)
|
||||
if ok {
|
||||
err = q.TrySendHigh(5)
|
||||
if err == nil {
|
||||
t.Errorf("expected high buffer to be full")
|
||||
}
|
||||
assumeRecvOk(5)
|
||||
@@ -118,10 +119,19 @@ func TestTrySendRecv(t *testing.T) {
|
||||
assumeRecvOk(7)
|
||||
assumeRecvOk(8)
|
||||
|
||||
_, ok = q.TryRecv()
|
||||
if ok {
|
||||
_, err = q.TryRecv()
|
||||
if err != priorityq.ErrEmpty {
|
||||
t.Errorf("expected queue to be empty")
|
||||
}
|
||||
q.Close()
|
||||
_, err = q.TryRecv()
|
||||
if err != priorityq.ErrClosed {
|
||||
t.Errorf("expected queue to be closed ")
|
||||
}
|
||||
err = q.TrySend(5)
|
||||
if err != priorityq.ErrClosed {
|
||||
t.Errorf("expected queue to be closed ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcProducerConsumer(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user