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:
2023-03-02 21:35:17 -08:00
parent b3b491d9a9
commit b00fe25128
12 changed files with 313 additions and 104 deletions

View File

@@ -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
}

View File

@@ -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) {