fix: restore tracing endpoint validation; honor per-side client timeouts; cache eviction target; redis close

- tracing: validateEndpoint used fmt.Sscanf("%s:%d") which treats %s as
  greedy, so every valid host:port endpoint failed and traced==nil disabled
  tracing entirely. Validate with net.SplitHostPort (pure helper, port range
  0-65535); test TestValidateEndpoint.
- proxy: createFasthttpClient used clientTimeout for every timeout slot,
  ignoring the parsed CLIENT_READ_TIMEOUT/CLIENT_WRITE_TIMEOUT (silent
  config loss, documented separately in README). Honour per-side read/write
  timeouts, falling back to client timeout when unset.
- cache/memory: evictToFreeMemory capped candidates at maxCacheSize/5 and
  stopped at the subset end, so a large write could leave the cache over
  its memory limit. Collect all entries and sort.Slice (correct + O(n log n)
  vs O(cap^2)), evict until the target is met or the cache is drained.
- cache/redis: the Redis cache client was never closed at shutdown (unlike
  the metrics aggregator). Add RedisConfig.Close, CacheWrapper.Close, a
  cache.Shutdown and register it with the ShutdownManager.
This commit is contained in:
2026-08-13 07:16:52 +01:00
parent fc28c65681
commit 14be2398e8
10 changed files with 167 additions and 37 deletions
+30 -3
View File
@@ -21,9 +21,7 @@ func TestNewTracing_NilContext_ReturnsError(t *testing.T) {
}
// TestNewTracing_InvalidEndpointFormats covers endpoint validation branches.
// Note: fmt.Sscanf("%s:%d") treats %s as greedy so any "host:port" string hits
// the format error (n!=2). The port-range branch (port>65535) requires n==2
// which Sscanf never produces for "host:port" strings — that's a source quirk.
// validateEndpoint rejects missing/host-only endpoints and out-of-range ports.
func TestNewTracing_InvalidEndpointFormats_ReturnsError(t *testing.T) {
tests := []struct {
name string
@@ -118,3 +116,32 @@ func TestExtractSpanContext_ValidTraceparent_ReturnsValid(t *testing.T) {
require.NoError(t, err)
assert.True(t, spanCtx.IsValid())
}
// TestValidateEndpoint checks the extracted endpoint validator. Regression for
// the old fmt.Sscanf("%s:%d") which rejected every valid "host:port"
// endpoint (crushing tracing into permanent no-op).
func TestValidateEndpoint(t *testing.T) {
for _, tt := range []struct {
name string
endpoint string
wantErr bool
}{
{name: "valid host:port", endpoint: "localhost:4317", wantErr: false},
{name: "valid fqdn:port", endpoint: "otel.example.com:4317", wantErr: false},
{name: "valid high port", endpoint: "collector:65535", wantErr: false},
{name: "missing port", endpoint: "localhost", wantErr: true},
{name: "port too high", endpoint: "localhost:999999", wantErr: true},
{name: "non-numeric port", endpoint: "localhost:abc", wantErr: true},
{name: "bare number", endpoint: "12345", wantErr: true},
} {
t.Run(tt.name, func(t *testing.T) {
err := validateEndpoint(tt.endpoint)
if tt.wantErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid endpoint format")
} else {
assert.NoError(t, err)
}
})
}
}