Files
metric/noop_test.go
Hanzo AI 1da854f420 noop: atomic counter — fix -race failure in concurrent handler workers
n.value++ on the noop counter was unguarded; concurrent workers in
luxfi/threshold/pkg/protocol/handler.go triggered -race failures
across protocols/{lss,frost} and pkg/thresholdd/TestCGGMP21RoundTrip.
Switch to atomic CAS over float64 bits stored in a uint64 — mirrors
metricCounter / metricGauge in metrics_impl.go so the noop and real
impls share the same internal representation (one and only one way).

Same pattern fixed in noopGauge (Set/Inc/Dec/Add/Sub/Get). noopHistogram,
noopSummary, and the *Vec wrappers are stateless / per-call fresh values
so they are race-free by construction and untouched.

Added TestNoop{Counter,Gauge}Concurrent regression guards (build-tag-free
so go test -race ./... continues to verify lock-free correctness).

Flagged by 2026-05-31 cross-repo audit.
2026-05-31 12:28:59 -07:00

59 lines
1.3 KiB
Go

// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package metric
import (
"sync"
"testing"
)
// TestNoopCounterConcurrent guards against the 2026-05-31 regression where
// noopCounter.value was a plain float64 mutated by `n.value++`, causing
// -race failures in threshold protocols.
func TestNoopCounterConcurrent(t *testing.T) {
c := &noopCounter{}
var wg sync.WaitGroup
for i := 0; i < 64; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
c.Inc()
c.Add(1)
_ = c.Get()
}
}()
}
wg.Wait()
if got, want := c.Get(), float64(64*1000*2); got != want {
t.Fatalf("noopCounter lost increments under contention: got %v want %v", got, want)
}
}
// TestNoopGaugeConcurrent guards the same regression for noopGauge.
func TestNoopGaugeConcurrent(t *testing.T) {
g := &noopGauge{}
var wg sync.WaitGroup
for i := 0; i < 64; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
g.Inc()
g.Add(1)
g.Dec()
g.Sub(1)
g.Set(0)
_ = g.Get()
}
}()
}
wg.Wait()
// Net delta per iteration is 0 (Inc+Add-Dec-Sub then Set(0)), so final
// must be exactly 0 if no updates were lost or torn.
if got := g.Get(); got != 0 {
t.Fatalf("noopGauge lost updates under contention: got %v want 0", got)
}
}