From 73c6a3aa37315a3e6f66185f1faa8e032e915d88 Mon Sep 17 00:00:00 2001 From: Alexey Dolotov Date: Mon, 30 Mar 2026 15:00:17 +0300 Subject: [PATCH] fix: tighten ScoutConnCollected encapsulation and add concurrency test - Move error check before Snapshot() to avoid unnecessary allocation - Update existing tests to use Snapshot() instead of direct field access - Add TestConcurrentAddSnapshot to explicitly exercise the mutex --- mtglib/internal/doppel/scout.go | 8 ++- .../doppel/scout_conn_collected_test.go | 52 +++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/mtglib/internal/doppel/scout.go b/mtglib/internal/doppel/scout.go index d90feba..24e06a7 100644 --- a/mtglib/internal/doppel/scout.go +++ b/mtglib/internal/doppel/scout.go @@ -61,10 +61,14 @@ func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) { client.CloseIdleConnections() } + if err != nil { + return ScoutResult{}, err + } + data, writeIndex := results.Snapshot() - if err != nil || len(data) == 0 { - return ScoutResult{}, err + if len(data) == 0 { + return ScoutResult{}, nil } var result ScoutResult diff --git a/mtglib/internal/doppel/scout_conn_collected_test.go b/mtglib/internal/doppel/scout_conn_collected_test.go index dcf8a41..fad45dd 100644 --- a/mtglib/internal/doppel/scout_conn_collected_test.go +++ b/mtglib/internal/doppel/scout_conn_collected_test.go @@ -1,6 +1,7 @@ package doppel import ( + "sync" "testing" "time" @@ -16,8 +17,10 @@ func (suite *ScoutConnCollectedTestSuite) TestAddSingle() { collected := NewScoutConnCollected() collected.Add(tls.TypeApplicationData, 100) - suite.Len(collected.data, 1) - suite.Equal(byte(tls.TypeApplicationData), collected.data[0].recordType) + data, _ := collected.Snapshot() + + suite.Len(data, 1) + suite.Equal(byte(tls.TypeApplicationData), data[0].recordType) } func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() { @@ -31,11 +34,52 @@ func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() { time.Sleep(time.Microsecond) collected.Add(tls.TypeApplicationData, 100) - for i := 1; i < len(collected.data); i++ { - suite.True(collected.data[i].timestamp.After(collected.data[i-1].timestamp)) + data, _ := collected.Snapshot() + + for i := 1; i < len(data); i++ { + suite.True(data[i].timestamp.After(data[i-1].timestamp)) } } +func (suite *ScoutConnCollectedTestSuite) TestConcurrentAddSnapshot() { + collected := NewScoutConnCollected() + + var wg sync.WaitGroup + + wg.Add(3) + + go func() { + defer wg.Done() + + for i := 0; i < 1000; i++ { + collected.Add(tls.TypeApplicationData, i) + } + }() + + go func() { + defer wg.Done() + + for i := 0; i < 100; i++ { + collected.MarkWrite() + } + }() + + go func() { + defer wg.Done() + + for i := 0; i < 1000; i++ { + data, _ := collected.Snapshot() + _ = len(data) + } + }() + + wg.Wait() + + data, writeIndex := collected.Snapshot() + suite.Len(data, 1000) + suite.GreaterOrEqual(writeIndex, 0) +} + func TestScoutConnCollected(t *testing.T) { t.Parallel() suite.Run(t, &ScoutConnCollectedTestSuite{})