bridgev2: BridgeStateQueue.prevSent is read/written without synchronization (data race)
Summary
BridgeStateQueue.prevSent (bridgev2/bridgestate.go) is a plain *status.BridgeState field that is:
- written, unsynchronized, by
immediateSendBridgeState(), which runs on the queue's own background goroutine started inNewBridgeStateQueue(go bsq.loop()) - read, unsynchronized, by
GetPrev(), which is documented/used as a way for callers on other goroutines to inspect the last state that was actually sent
func (bsq *BridgeStateQueue) immediateSendBridgeState(state status.BridgeState) {
...
bsq.prevSent = &state // write, on the loop() goroutine
...
}
func (bsq *BridgeStateQueue) GetPrev() status.BridgeState {
if bsq != nil && bsq.prevSent != nil { // read, from any caller's goroutine
return *bsq.prevSent
}
return status.BridgeState{}
}
go test -race reliably flags this as a data race whenever a test polls UserLogin.BridgeState.GetPrev() shortly after calling something that triggers a Send() (e.g. NetworkAPI.Connect).
Reproduction
Any bridgev2-based test that does roughly:
c.Connect(ctx) // eventually calls UserLogin.BridgeState.Send(...) from a background goroutine
for {
got := ul.BridgeState.GetPrev()
if got.StateEvent == wantEvent {
break
}
time.Sleep(20 * time.Millisecond)
}
...run under go test -race ./... reports:
WARNING: DATA RACE
Write at 0x... by goroutine N:
maunium.net/go/mautrix/bridgev2.(*BridgeStateQueue).immediateSendBridgeState()
bridgev2/bridgestate.go:284
Previous read at 0x... by goroutine M:
maunium.net/go/mautrix/bridgev2.(*BridgeStateQueue).GetPrev()
bridgev2/bridgestate.go:316
(and the mirror-image write/write and read/write races on the same field, since Send()'s producer side and the reader race concurrently).
Suggested fix
BridgeStateQueue already uses atomic.Pointer[T] elsewhere in the same struct (cancelScheduledNotice, stopReconnect) for exactly this kind of cross-goroutine pointer access. Switching prevSent (and probably prevUnsent, which has the same shape) to atomic.Pointer[status.BridgeState] (or guarding both with a mutex) would fix this without changing the public API - GetPrev()'s signature can stay the same.
Environment
maunium.net/go/mautrixv0.29.0- Found while adding
-racecoverage to an unrelated project's bridgev2-based tests; can provide a minimal standalone repro if useful.
Source: mautrix/go