mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: separate pubsub into a new package (#8017)
* chore: rename store to dbmock for consistency * chore: remove redundant dbtype package This wasn't necessary and forked how we do DB types. * chore: separate pubsub into a new package This didn't need to be in database and was bloating it.
This commit is contained in:
@@ -11,13 +11,14 @@ import (
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbfake"
|
||||
"github.com/coder/coder/coderd/database/postgres"
|
||||
"github.com/coder/coder/coderd/database/pubsub"
|
||||
)
|
||||
|
||||
func NewDB(t testing.TB) (database.Store, database.Pubsub) {
|
||||
func NewDB(t testing.TB) (database.Store, pubsub.Pubsub) {
|
||||
t.Helper()
|
||||
|
||||
db := dbfake.New()
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
ps := pubsub.NewInMemory()
|
||||
if os.Getenv("DB") != "" {
|
||||
connectionURL := os.Getenv("CODER_PG_CONNECTION_URL")
|
||||
if connectionURL == "" {
|
||||
@@ -36,12 +37,12 @@ func NewDB(t testing.TB) (database.Store, database.Pubsub) {
|
||||
})
|
||||
db = database.New(sqlDB)
|
||||
|
||||
pubsub, err = database.NewPubsub(context.Background(), sqlDB, connectionURL)
|
||||
ps, err = pubsub.New(context.Background(), sqlDB, connectionURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = pubsub.Close()
|
||||
_ = ps.Close()
|
||||
})
|
||||
}
|
||||
|
||||
return db, pubsub
|
||||
return db, ps
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -48,7 +48,7 @@ type msgOrErr struct {
|
||||
type msgQueue struct {
|
||||
ctx context.Context
|
||||
cond *sync.Cond
|
||||
q [PubsubBufferSize]msgOrErr
|
||||
q [BufferSize]msgOrErr
|
||||
front int
|
||||
size int
|
||||
closed bool
|
||||
@@ -82,7 +82,7 @@ func (q *msgQueue) run() {
|
||||
return
|
||||
}
|
||||
item := q.q[q.front]
|
||||
q.front = (q.front + 1) % PubsubBufferSize
|
||||
q.front = (q.front + 1) % BufferSize
|
||||
q.size--
|
||||
q.cond.L.Unlock()
|
||||
|
||||
@@ -111,20 +111,20 @@ func (q *msgQueue) enqueue(msg []byte) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
if q.size == PubsubBufferSize {
|
||||
if q.size == BufferSize {
|
||||
// queue is full, so we're going to drop the msg we got called with.
|
||||
// We also need to record that messages are being dropped, which we
|
||||
// do at the last message in the queue. This potentially makes us
|
||||
// lose 2 messages instead of one, but it's more important at this
|
||||
// point to warn the subscriber that they're losing messages so they
|
||||
// can do something about it.
|
||||
back := (q.front + PubsubBufferSize - 1) % PubsubBufferSize
|
||||
back := (q.front + BufferSize - 1) % BufferSize
|
||||
q.q[back].msg = nil
|
||||
q.q[back].err = ErrDroppedMessages
|
||||
return
|
||||
}
|
||||
// queue is not full, insert the message
|
||||
next := (q.front + q.size) % PubsubBufferSize
|
||||
next := (q.front + q.size) % BufferSize
|
||||
q.q[next].msg = msg
|
||||
q.q[next].err = nil
|
||||
q.size++
|
||||
@@ -143,17 +143,17 @@ func (q *msgQueue) dropped() {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
if q.size == PubsubBufferSize {
|
||||
if q.size == BufferSize {
|
||||
// queue is full, but we need to record that messages are being dropped,
|
||||
// which we do at the last message in the queue. This potentially drops
|
||||
// another message, but it's more important for the subscriber to know.
|
||||
back := (q.front + PubsubBufferSize - 1) % PubsubBufferSize
|
||||
back := (q.front + BufferSize - 1) % BufferSize
|
||||
q.q[back].msg = nil
|
||||
q.q[back].err = ErrDroppedMessages
|
||||
return
|
||||
}
|
||||
// queue is not full, insert the error
|
||||
next := (q.front + q.size) % PubsubBufferSize
|
||||
next := (q.front + q.size) % BufferSize
|
||||
q.q[next].msg = nil
|
||||
q.q[next].err = ErrDroppedMessages
|
||||
q.size++
|
||||
@@ -171,9 +171,9 @@ type pgPubsub struct {
|
||||
queues map[string]map[uuid.UUID]*msgQueue
|
||||
}
|
||||
|
||||
// PubsubBufferSize is the maximum number of unhandled messages we will buffer
|
||||
// BufferSize is the maximum number of unhandled messages we will buffer
|
||||
// for a subscriber before dropping messages.
|
||||
const PubsubBufferSize = 2048
|
||||
const BufferSize = 2048
|
||||
|
||||
// Subscribe calls the listener when an event matching the name is received.
|
||||
func (p *pgPubsub) Subscribe(event string, listener Listener) (cancel func(), err error) {
|
||||
@@ -295,8 +295,8 @@ func (p *pgPubsub) recordReconnect() {
|
||||
}
|
||||
}
|
||||
|
||||
// NewPubsub creates a new Pubsub implementation using a PostgreSQL connection.
|
||||
func NewPubsub(ctx context.Context, database *sql.DB, connectURL string) (Pubsub, error) {
|
||||
// New creates a new Pubsub implementation using a PostgreSQL connection.
|
||||
func New(ctx context.Context, database *sql.DB, connectURL string) (Pubsub, error) {
|
||||
// Creates a new listener using pq.
|
||||
errCh := make(chan error)
|
||||
listener := pq.NewListener(connectURL, time.Second, time.Minute, func(_ pq.ListenerEventType, err error) {
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -26,7 +26,7 @@ func Test_msgQueue_ListenerWithError(t *testing.T) {
|
||||
// PubsubBufferSize is 2048, which is a power of 2, so a pattern of 5 will not be aligned
|
||||
// when we wrap around the end of the circular buffer. This tests that we correctly handle
|
||||
// the wrapping and aren't dequeueing misaligned data.
|
||||
cycles := (PubsubBufferSize / 5) * 2 // almost twice around the ring
|
||||
cycles := (BufferSize / 5) * 2 // almost twice around the ring
|
||||
for j := 0; j < cycles; j++ {
|
||||
for i := 0; i < 4; i++ {
|
||||
uut.enqueue([]byte(fmt.Sprintf("%d%d", j, i)))
|
||||
@@ -75,7 +75,7 @@ func Test_msgQueue_Listener(t *testing.T) {
|
||||
// PubsubBufferSize is 2048, which is a power of 2, so a pattern of 5 will not be aligned
|
||||
// when we wrap around the end of the circular buffer. This tests that we correctly handle
|
||||
// the wrapping and aren't dequeueing misaligned data.
|
||||
cycles := (PubsubBufferSize / 5) * 2 // almost twice around the ring
|
||||
cycles := (BufferSize / 5) * 2 // almost twice around the ring
|
||||
for j := 0; j < cycles; j++ {
|
||||
for i := 0; i < 4; i++ {
|
||||
uut.enqueue([]byte(fmt.Sprintf("%d%d", j, i)))
|
||||
@@ -119,7 +119,7 @@ func Test_msgQueue_Full(t *testing.T) {
|
||||
// we send 2 more than the capacity. One extra because the call to the ListenerFunc blocks
|
||||
// but only after we've dequeued a message, and then another extra because we want to exceed
|
||||
// the capacity, not just reach it.
|
||||
for i := 0; i < PubsubBufferSize+2; i++ {
|
||||
for i := 0; i < BufferSize+2; i++ {
|
||||
uut.enqueue([]byte(fmt.Sprintf("%d", i)))
|
||||
// ensure the first dequeue has happened before proceeding, so that this function isn't racing
|
||||
// against the goroutine that dequeues items.
|
||||
@@ -136,5 +136,5 @@ func Test_msgQueue_Full(t *testing.T) {
|
||||
// Ok, so we sent 2 more than capacity, but we only read the capacity, that's because the last
|
||||
// message we send doesn't get queued, AND, it bumps a message out of the queue to make room
|
||||
// for the error, so we read 2 less than we sent.
|
||||
require.Equal(t, PubsubBufferSize, n)
|
||||
require.Equal(t, BufferSize, n)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package database
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -87,7 +87,7 @@ func (*memoryPubsub) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPubsubInMemory() Pubsub {
|
||||
func NewInMemory() Pubsub {
|
||||
return &memoryPubsub{
|
||||
listeners: make(map[string]map[uuid.UUID]genericListener),
|
||||
}
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package database_test
|
||||
package pubsub_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/pubsub"
|
||||
)
|
||||
|
||||
func TestPubsubMemory(t *testing.T) {
|
||||
@@ -16,7 +16,7 @@ func TestPubsubMemory(t *testing.T) {
|
||||
t.Run("Legacy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
pubsub := pubsub.NewInMemory()
|
||||
event := "test"
|
||||
data := "testing"
|
||||
messageChannel := make(chan []byte)
|
||||
@@ -36,7 +36,7 @@ func TestPubsubMemory(t *testing.T) {
|
||||
t.Run("WithErr", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
pubsub := pubsub.NewInMemory()
|
||||
event := "test"
|
||||
data := "testing"
|
||||
messageChannel := make(chan []byte)
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build linux
|
||||
|
||||
package database_test
|
||||
package pubsub_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/postgres"
|
||||
"github.com/coder/coder/coderd/database/pubsub"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestPubsub(t *testing.T) {
|
||||
db, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
pubsub, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
event := "test"
|
||||
@@ -67,7 +67,7 @@ func TestPubsub(t *testing.T) {
|
||||
db, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
pubsub, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
cancelFunc()
|
||||
@@ -82,7 +82,7 @@ func TestPubsub(t *testing.T) {
|
||||
db, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
pubsub, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestPubsub(t *testing.T) {
|
||||
db, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
pubsub, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
|
||||
@@ -171,12 +171,12 @@ func TestPubsub_ordering(t *testing.T) {
|
||||
db, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
ps, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
defer ps.Close()
|
||||
event := "test"
|
||||
messageChannel := make(chan []byte, 100)
|
||||
cancelSub, err := pubsub.Subscribe(event, func(ctx context.Context, message []byte) {
|
||||
cancelSub, err := ps.Subscribe(event, func(ctx context.Context, message []byte) {
|
||||
// sleep a random amount of time to simulate handlers taking different amount of time
|
||||
// to process, depending on the message
|
||||
// nolint: gosec
|
||||
@@ -187,7 +187,7 @@ func TestPubsub_ordering(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer cancelSub()
|
||||
for i := 0; i < 100; i++ {
|
||||
err = pubsub.Publish(event, []byte(fmt.Sprintf("%d", i)))
|
||||
err = ps.Publish(event, []byte(fmt.Sprintf("%d", i)))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
@@ -219,14 +219,14 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitSuperLong)
|
||||
defer cancelFunc()
|
||||
pubsub, err := database.NewPubsub(ctx, db, connectionURL)
|
||||
ps, err := pubsub.New(ctx, db, connectionURL)
|
||||
require.NoError(t, err)
|
||||
defer pubsub.Close()
|
||||
defer ps.Close()
|
||||
event := "test"
|
||||
|
||||
// buffer responses so that when the test completes, goroutines don't get blocked & leak
|
||||
errors := make(chan error, database.PubsubBufferSize)
|
||||
messages := make(chan string, database.PubsubBufferSize)
|
||||
errors := make(chan error, pubsub.BufferSize)
|
||||
messages := make(chan string, pubsub.BufferSize)
|
||||
readOne := func() (m string, e error) {
|
||||
t.Helper()
|
||||
select {
|
||||
@@ -244,7 +244,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
return m, e
|
||||
}
|
||||
|
||||
cancelSub, err := pubsub.SubscribeWithErr(event, func(ctx context.Context, msg []byte, err error) {
|
||||
cancelSub, err := ps.SubscribeWithErr(event, func(ctx context.Context, msg []byte, err error) {
|
||||
messages <- string(msg)
|
||||
errors <- err
|
||||
})
|
||||
@@ -252,7 +252,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
defer cancelSub()
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
err = pubsub.Publish(event, []byte(fmt.Sprintf("%d", i)))
|
||||
err = ps.Publish(event, []byte(fmt.Sprintf("%d", i)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// make sure we're getting at least one message.
|
||||
@@ -270,7 +270,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
err = pubsub.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
err = ps.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
j++
|
||||
if err != nil {
|
||||
break
|
||||
@@ -292,7 +292,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
err = pubsub.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
err = ps.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
k := j
|
||||
// exceeding the buffer invalidates the test because this causes us to drop messages for reasons other than DB
|
||||
// reconnect
|
||||
require.Less(t, k, database.PubsubBufferSize, "exceeded buffer")
|
||||
require.Less(t, k, pubsub.BufferSize, "exceeded buffer")
|
||||
|
||||
// We don't know how quickly the pubsub will reconnect, so continue to send messages with increasing numbers. As
|
||||
// soon as we see k or higher we know we're getting messages after the restart.
|
||||
@@ -315,7 +315,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
_ = pubsub.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
_ = ps.Publish(event, []byte(fmt.Sprintf("%d", j)))
|
||||
j++
|
||||
time.Sleep(testutil.IntervalFast)
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
gotDroppedErr := false
|
||||
for {
|
||||
m, err := readOne()
|
||||
if xerrors.Is(err, database.ErrDroppedMessages) {
|
||||
if xerrors.Is(err, pubsub.ErrDroppedMessages) {
|
||||
gotDroppedErr = true
|
||||
continue
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func TestPubsub_Disconnect(t *testing.T) {
|
||||
if l >= k {
|
||||
// exceeding the buffer invalidates the test because this causes us to drop messages for reasons other than
|
||||
// DB reconnect
|
||||
require.Less(t, l, database.PubsubBufferSize, "exceeded buffer")
|
||||
require.Less(t, l, pubsub.BufferSize, "exceeded buffer")
|
||||
break
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user