Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#547·go

bridgev2: BridgeStateQueue.prevSent is read/written without synchronization (data race)

Author: flojonCreated Aug 14, 2026Updated Aug 14, 2026

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 in NewBridgeStateQueue (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/mautrix v0.29.0
  • Found while adding -race coverage to an unrelated project's bridgev2-based tests; can provide a minimal standalone repro if useful.

Source: mautrix/go

View original on GitHubView discussion on GitHub