8000 fix: guard gateway & voice shared state against data races by sealbro · Pull Request #559 · disgoorg/disgo · GitHub
[go: up one dir, main page]

Skip to content

fix: guard gateway & voice shared state against data races - #559

Open
sealbro wants to merge 7 commits into
disgoorg:masterfrom
sealbro:fix/gateway-data-races
Open

sealbro wants to merge 7 commits into
disgoorg:masterfrom
sealbro:fix/gateway-data-races

Conversation

@sealbro
@sealbro sealbro commented Jun 27, 2026 •
Copy link
Copy Markdown
Contributor

Summary

Heartbeat state in both gateways (cancel func, interval, sent/received timestamps, nonce) and the voice gateway's SSRC/seq were accessed from several goroutines without synchronization. This guards them with mutexes, and starts the heartbeat with its cancel func set before the goroutine runs, so a concurrent close can no longer miss it.

Session state is left as is: it's already guarded upstream since #634. The audio sender/receiver are left to #604.

Testing

  • go test --race ./...
  • go build ./...

@sealbro
sealbro force-pushed the fix/gateway-data-races branch from 8f34477 to 076c470 Compare October 25, 2026 23:01
@sealbro
sealbro force-pushed the fix/gateway-data-races branch from 076c470 to 140eff6 Compare October 25, 2026 23:05
@thomas-vilte
Copy link
Copy Markdown
Contributor

the gateway changes look good to me (heartbeat cancel set before starting the goroutine, heartbeat times, nonce, seq/ssrc). build, vet and go test -race ./... all pass.

there's a regression in the audio part though. with defer s.Close() in the sender loop, opusProvider.Close() gets called twice: once by whoever closes the sender, then again when the loop exits.

type countingProvider struct{ closes atomic.Int32 }

func (*countingProvider) ProvideOpusFrame() ([]byte, error) { return nil, nil }
func (p *countingProvider) Close()                          { p.closes.Add(1) }

func TestSenderClosesProviderOnce(t *testing.T) {
	p := &countingProvider{}
	s := NewAudioSender(slog.Default(), p, &connImpl{dave: notReadyDave{}}) // Ready() returns false
	s.Open()
	time.Sleep(50 * time.Millisecond)
	s.Close()
	time.Sleep(50 * time.Millisecond)
	if n := p.closes.Load(); n != 1 {
		t.Fatalf("provider closed %d times", n)
	}
}

this passes on master and fails here with 2 closes. that can break providers whose Close() isn't idempotent, like one that closes a channel.

with a nil provider (SetOpusFrameProvider(nil)), it gets worse. on master, Close() panics in the caller, which can recover it. here the second panic happens in the sender goroutine. the caller can't recover that one, so it takes down the process.

#604 makes the same cancelFunc change for the sender/receiver and also waits until UDP is ready to start the loops. the two PRs conflict in those files, so maybe drop the audio changes from this one and keep the gateway part? that would remove the overlap.

one minor thing: heartbeatInterval is still written in listen and read by the heartbeat goroutine without the lock.

@sealbro
sealbro commented Oct 27, 2026
Copy link
Copy Markdown
Contributor Author

Thanks for the review. Pushed 75309eb:

go test --race ./... passes.

@thomas-vilte
Copy link
Copy Markdown
Contributor

looks good now. voice/gateway.go still has the same issue though: heartbeatInterval is written in listen (line 649) and read in heartbeat and sendHeartbeat (lines 481 and 507) without the lock. I didn't specify which gateway in my comment, sorry about that. same fix there and lgtm from me

also tested this together with #604 on current master. no merge conflicts, and go test -race ./... passes

@sealbro
sealbro commented Oct 28, 2026
Copy link
Copy Markdown
Contributor Author

Thanks for catching that. Fixed the same race in voice/gateway.go in 9dde3e0: the interval is now passed from the hello handler to the heartbeat goroutine instead of living in a shared field. go test --race ./... passes.

Comment thread gateway/gateway.go Resolved
@topi314
topi314 commented Oct 29, 2026
Copy link
Copy Markdown
Member

ci needs to be fixed

@sealbro
sealbro commented Oct 29, 2026
Copy link
Copy Markdown
Contributor Author

ci needs to be fixed

Fixed CI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants

0