proto-socket/go/inbound_gateway_test.go
toki 2ec43f2d00 feat: inbound queue ordering - Go gateway implementation
- Add inbound_gateway.go and inbound_gateway_test.go
- Update communicator.go with nonce handling improvements
- Update tcp_client.go and ws_client.go
- Update communicator_test.go and ws_test.go
- Move old Go gateway docs to archive
2026-06-02 11:43:10 +09:00

145 lines
3.8 KiB
Go

package proto_socket
import (
"sync"
"testing"
"time"
"google.golang.org/protobuf/proto"
"git.toki-labs.com/toki/proto-socket/go/packets"
)
// TestFrameReorderBufferReleasesInOrder: out-of-order results are buffered and
// released only once the contiguous prefix is complete.
func TestFrameReorderBufferReleasesInOrder(t *testing.T) {
b := newFrameReorderBuffer()
if got := b.release(workerResult{seq: 3}); len(got) != 0 {
t.Fatalf("seq 3 should buffer until 1,2 arrive, released %d", len(got))
}
if got := b.release(workerResult{seq: 2}); len(got) != 0 {
t.Fatalf("seq 2 should still buffer until 1 arrives, released %d", len(got))
}
got := b.release(workerResult{seq: 1})
if len(got) != 3 {
t.Fatalf("expected 1,2,3 to flush together, got %d", len(got))
}
for i, r := range got {
if r.seq != int64(i+1) {
t.Fatalf("reorder violated: position %d has seq %d", i, r.seq)
}
}
// A seq below nextSeq is a duplicate/late arrival and is ignored.
if got := b.release(workerResult{seq: 1}); len(got) != 0 {
t.Fatalf("late duplicate seq should be ignored, released %d", len(got))
}
}
// TestWorkerGatewayReordersOutOfOrderCompletion: workers that finish out of
// order still reach the sink in input seq order. Earlier seqs are made to decode
// slower so the pool genuinely completes them last.
func TestWorkerGatewayReordersOutOfOrderCompletion(t *testing.T) {
const total = 8
var mu sync.Mutex
var order []int32
parse := func(raw []byte) (*packets.PacketBase, error) {
base := &packets.PacketBase{}
if err := proto.Unmarshal(raw, base); err != nil {
return nil, err
}
// Lower nonce (earlier seq) sleeps longer to force reversed completion.
delay := time.Duration(total-base.GetNonce()) * 5 * time.Millisecond
time.Sleep(delay)
return base, nil
}
done := make(chan struct{})
sink := func(f DecodedFrame) {
mu.Lock()
order = append(order, f.IncomingNonce)
n := len(order)
mu.Unlock()
if n == total {
close(done)
}
}
g := newWorkerGateway(4, 16, parse, sink, nil)
defer g.Close()
for seq := int64(1); seq <= total; seq++ {
data, err := proto.Marshal(&packets.PacketBase{TypeName: "x", Nonce: int32(seq)})
if err != nil {
t.Fatal(err)
}
g.Submit(InboundFrame{Seq: seq, Bytes: data})
}
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for gateway results")
}
mu.Lock()
defer mu.Unlock()
for i, n := range order {
if n != int32(i+1) {
t.Fatalf("seq ordering not preserved at position %d: %v", i, order)
}
}
}
// TestWorkerGatewayReportsDecodeErrorAndAdvances: a frame that fails to decode is
// reported to onError (not the sink) and does not stall later frames; subsequent
// good frames still reach the sink in order.
func TestWorkerGatewayReportsDecodeErrorAndAdvances(t *testing.T) {
var mu sync.Mutex
var seen []int32
var errCount int
done := make(chan struct{})
sink := func(f DecodedFrame) {
mu.Lock()
seen = append(seen, f.IncomingNonce)
n := len(seen)
mu.Unlock()
if n == 2 {
close(done)
}
}
onError := func(error) {
mu.Lock()
errCount++
mu.Unlock()
}
g := NewWorkerGateway(2, 8, sink, onError)
defer g.Close()
good1, _ := proto.Marshal(&packets.PacketBase{TypeName: "x", Nonce: 1})
good2, _ := proto.Marshal(&packets.PacketBase{TypeName: "x", Nonce: 3})
g.Submit(InboundFrame{Seq: 1, Bytes: good1})
g.Submit(InboundFrame{Seq: 2, Bytes: []byte{0xff, 0xff, 0xff}}) // undecodable
g.Submit(InboundFrame{Seq: 3, Bytes: good2})
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("timed out; a decode error stalled the reorder window")
}
mu.Lock()
defer mu.Unlock()
if len(seen) != 2 || seen[0] != 1 || seen[1] != 3 {
t.Fatalf("expected sink to see [1 3] skipping the bad frame, got %v", seen)
}
if errCount != 1 {
t.Fatalf("expected exactly one decode error reported, got %d", errCount)
}
}