fix: stabilize flaky CI tests

1. Add sync.Mutex to ScoutConnCollected to eliminate data race between
   Add()/MarkWrite() in readLoop and learn() iterating results.
   Introduce Snapshot() for safe read access.

2. Increase bloom filter test size from 500 to 100000 to prevent
   false negatives from random eviction in the stable bloom filter.

3. Use Require().NoError() in TestHTTPSRequest to prevent nil-pointer
   panic on resp.Body.Close() when the request fails.

Fixes #425
This commit is contained in:
Alexey Dolotov
2026-03-30 14:50:32 +03:00
parent a2de52f071
commit e54d9d60d3
4 changed files with 30 additions and 9 deletions
+8 -6
View File
@@ -61,7 +61,9 @@ func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) {
client.CloseIdleConnections()
}
if err != nil || len(results.data) == 0 {
data, writeIndex := results.Snapshot()
if err != nil || len(data) == 0 {
return ScoutResult{}, err
}
@@ -70,14 +72,14 @@ func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) {
// Compute inter-record durations (existing logic).
lastTimestamp := time.Time{}
for i, v := range results.data {
for i, v := range data {
if v.recordType != tls.TypeApplicationData {
continue
}
if lastTimestamp.IsZero() {
if i > 0 {
lastTimestamp = results.data[i-1].timestamp
lastTimestamp = data[i-1].timestamp
} else {
lastTimestamp = v.timestamp
}
@@ -90,12 +92,12 @@ func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) {
// Compute cert size: sum of ApplicationData payload between CCS and
// the first client Write (which marks the end of server handshake).
seenCCS := false
boundary := results.writeIndex
boundary := writeIndex
if boundary < 0 {
boundary = len(results.data)
boundary = len(data)
}
for i, v := range results.data {
for i, v := range data {
if i >= boundary {
break
}
+20 -1
View File
@@ -1,6 +1,9 @@
package doppel
import "time"
import (
"sync"
"time"
)
const (
ScoutConnCollectedPreallocSize = 100
@@ -13,23 +16,39 @@ type ScoutConnResult struct {
}
type ScoutConnCollected struct {
mu sync.Mutex
data []ScoutConnResult
writeIndex int // index at which client first wrote post-handshake data; -1 if not set
}
func (s *ScoutConnCollected) Add(record byte, payloadLen int) {
s.mu.Lock()
s.data = append(s.data, ScoutConnResult{
timestamp: time.Now(),
recordType: record,
payloadLen: payloadLen,
})
s.mu.Unlock()
}
// MarkWrite records the current data length as the handshake boundary.
func (s *ScoutConnCollected) MarkWrite() {
s.mu.Lock()
if s.writeIndex < 0 {
s.writeIndex = len(s.data)
}
s.mu.Unlock()
}
// Snapshot returns a copy of the collected data and the write index.
func (s *ScoutConnCollected) Snapshot() ([]ScoutConnResult, int) {
s.mu.Lock()
snapshot := make([]ScoutConnResult, len(s.data))
copy(snapshot, s.data)
writeIndex := s.writeIndex
s.mu.Unlock()
return snapshot, writeIndex
}
func NewScoutConnCollected() *ScoutConnCollected {
+1 -1
View File
@@ -175,7 +175,7 @@ func (suite *ProxyTestSuite) TestHTTPSRequest() {
addr := fmt.Sprintf("https://%s/headers", suite.ProxyAddress())
resp, err := client.Get(addr) //nolint: noctx
suite.NoError(err)
suite.Require().NoError(err)
defer resp.Body.Close() //nolint: errcheck