- 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
145 lines
3.8 KiB
Go
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)
|
|
}
|
|
}
|