@@ -259,3 +259,61 @@ func TestCircuitBreaker_ConcurrentAccess(t *testing.T) {
259259 state := cb .GetState ()
260260 assert .Contains (t , []CircuitBreakerState {StateClosed , StateOpen , StateHalfOpen }, state )
261261}
262+
263+ func TestPeerCircuitBreakers_GetPeerState_DataRace (t * testing.T ) {
264+ config := CircuitBreakerConfig {
265+ FailureThreshold : 5 ,
266+ SuccessThreshold : 2 ,
267+ Timeout : 10 * time .Millisecond ,
268+ MaxHalfOpenRequests : 1 ,
269+ }
270+ pcb := NewPeerCircuitBreakers (config )
271+ peerID := "test-peer-001"
272+
273+ // Create the breaker
274+ breaker := pcb .GetBreaker (peerID )
275+
276+ // Start goroutines that modify breaker.state
277+ done := make (chan bool , 3 )
278+
279+ // Goroutine 1: Record failures
280+ go func () {
281+ for i := 0 ; i < 1000 ; i ++ {
282+ breaker .RecordFailure ()
283+ }
284+ done <- true
285+ }()
286+
287+ // Goroutine 2: Record successes
288+ go func () {
289+ for i := 0 ; i < 1000 ; i ++ {
290+ breaker .RecordSuccess ()
291+ }
292+ done <- true
293+ }()
294+
295+ // Goroutine 3: Call CanCall (which also modifies state)
296+ go func () {
297+ for i := 0 ; i < 1000 ; i ++ {
298+ breaker .CanCall ()
299+ }
300+ done <- true
301+ }()
302+
303+ // Main goroutine: Continuously read state via GetPeerState
304+ // This reads breaker.state via thread-safe GetState() method
305+ readCount := 0
306+ for {
307+ select {
308+ case <- done :
309+ readCount ++
310+ if readCount >= 3 {
311+ return // All writers done
312+ }
313+ default :
314+ // This should be safe now - accessing breaker.state
315+ // via GetState() which holds breaker.mu
316+ _ = pcb .GetPeerState (peerID )
317+ }
318+ }
319+ }
0 commit comments