90 lines
2.1 KiB
Go
90 lines
2.1 KiB
Go
package integration
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
|
)
|
|
|
|
type blockingOutboxPublisher struct {
|
|
entered chan struct{}
|
|
release chan struct{}
|
|
|
|
mu sync.Mutex
|
|
inFlight int
|
|
maxInFlight int
|
|
count int
|
|
}
|
|
|
|
func newBlockingOutboxPublisher() *blockingOutboxPublisher {
|
|
return &blockingOutboxPublisher{
|
|
entered: make(chan struct{}, 2),
|
|
release: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (p *blockingOutboxPublisher) PublishOutboxEvent(ctx context.Context, _ *roomeventsv1.EventEnvelope) error {
|
|
p.mu.Lock()
|
|
p.inFlight++
|
|
if p.inFlight > p.maxInFlight {
|
|
p.maxInFlight = p.inFlight
|
|
}
|
|
p.count++
|
|
p.mu.Unlock()
|
|
|
|
p.entered <- struct{}{}
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-p.release:
|
|
}
|
|
|
|
p.mu.Lock()
|
|
p.inFlight--
|
|
p.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (p *blockingOutboxPublisher) snapshot() (int, int) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
return p.count, p.maxInFlight
|
|
}
|
|
|
|
func TestShardedOutboxPublisherSerializesSameRoom(t *testing.T) {
|
|
inner := newBlockingOutboxPublisher()
|
|
publisher := NewShardedOutboxPublisher(inner, 8)
|
|
envelope := &roomeventsv1.EventEnvelope{RoomId: "room-same"}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
// 两个同房间事件同时进入时,分片锁必须让第二条等第一条完成后再调用真实 IM 发布器。
|
|
errs := make(chan error, 2)
|
|
go func() { errs <- publisher.PublishOutboxEvent(ctx, envelope) }()
|
|
<-inner.entered
|
|
go func() { errs <- publisher.PublishOutboxEvent(ctx, envelope) }()
|
|
|
|
select {
|
|
case <-inner.entered:
|
|
t.Fatal("same room event entered inner publisher before the first event finished")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
|
|
inner.release <- struct{}{}
|
|
if err := <-errs; err != nil {
|
|
t.Fatalf("first publish failed: %v", err)
|
|
}
|
|
<-inner.entered
|
|
inner.release <- struct{}{}
|
|
if err := <-errs; err != nil {
|
|
t.Fatalf("second publish failed: %v", err)
|
|
}
|
|
|
|
if count, maxInFlight := inner.snapshot(); count != 2 || maxInFlight != 1 {
|
|
t.Fatalf("same room publishes must be serialized, count=%d max_in_flight=%d", count, maxInFlight)
|
|
}
|
|
}
|