8000 fix(voice): recover connections with fresh sessions by kota113 · Pull Request #587 · disgoorg/disgo · GitHub
[go: up one dir, main page]

Skip to content

fix(voice): recover connections with fresh sessions - #587

Merged
topi314 merged 5 commits into
disgoorg:masterfrom
kota113:fix/voice-reconnect-fresh-session
Aug 25, 2026
Merged

topi314 merged 5 commits into
disgoorg:masterfrom
kota113:fix/voice-reconnect-fresh-session

Conversation

@kota113
@kota113 kota113 commented Aug 11, 2026 •
Copy link
Copy Markdown
Contributor

Summary

  • Limit Voice Gateway resume attempts and use a capped exponential backoff.
  • Request a fresh voice session when resuming the existing session is no longer possible.
  • Wait for both fresh Voice State and Voice Server updates before opening the Voice Gateway.
  • Do not reconnect after rate-limited or terminated-call close events.

Problem

When a Voice Gateway endpoint became unavailable, the reconnect loop kept retrying the cached endpoint indefinitely. The previous delay calculation also multiplied the attempt count by an already increasing delay, allowing the interval to grow beyond the documented 10-second limit.

If those attempts eventually failed, the connection could not recover by requesting new voice credentials. This left the connection registered but unable to send speaking updates until the process was restarted.

The connection could also open as soon as a Voice Server Update supplied a token and endpoint, without waiting for the matching fresh session ID from a Voice State Update.

Changes

  • Retry Voice Gateway resume up to five times with delays capped at 10 seconds.
  • Close a Voice Gateway with a mismatched heartbeat ACK before reconnecting it.
  • Treat exhausted resume attempts and close codes requiring a new connection as reasons to:
    • clear the cached session ID, token, and endpoint;
    • close the stale UDP connection;
    • send a new Voice State Update; and
    • reconnect only after fresh state and server updates are both available.
  • Reset voice sequencing state when switching to a new session.
  • Correct close codes 4021 and 4022 so they do not start a new connection, matching Discord's documented behavior.

Testing

  • Added coverage for capped reconnect delays.
  • Added coverage for close events that must not reconnect.
  • Added coverage for requesting a fresh connection after an invalid session or exhausted resume attempts.
  • Added coverage ensuring the Voice Gateway waits for both fresh update events.
  • Ran go test -race ./voice.
  • Ran go test ./....
  • Ran go vet ./....

@kota113
kota113 commented Aug 11, 2026
Copy link
Copy Markdown
Contributor Author

I noticed it might partially conflict with #582. I am happy to make the changes.

@kota113
kota113 force-pushed the fix/voice-reconnect-fresh-session branch from f539585 to c44e810 Compare August 11, 2026 05:51
@thomas-vilte thomas-vilte left a comment •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulled the branch and actually ran things instead of just reading the diff — build/vet clean, tests pass with -race. also pointed our bot at this branch and ran the test we wrote for the exact bug we hit in prod, still passes fine

the part I actually care about: TestHandleGatewayCloseStartsFreshConnection's "resume attempts exhausted" case feeds it a plain error, same shape as a dns failure, and it correctly asks for a fresh session instead of looping forever. that's literally our bug

couple small things, not blockers - there's no test that goes through reconnect() -> 5 tries -> closeHandlerFunc in one shot, it's just tested in pieces (easy enough to verify by eye though). also tryOpenGateway's timeout went from 5s to 30s for every open attempt, not just recovery, so a join that's actually gonna fail takes longer to error now. probably fine

separate heads up, this touches the same handleGatewayClose that #580 and #583 both edit, so those are gonna conflict

approving, looks good to me

Comment thread voice/conn.go Resolved
Comment thread voice/gateway.go Resolved
@thomas-vilte thomas-vilte left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

found two things while we were going through the branch — left them as inline suggestions, both should be quick to apply

@kota113
kota113 commented Aug 12, 2026
Copy link
Copy Markdown
Contributor Author

Thanks for your suggestion. I edited both sections reflecting your review.

Comment thread voice/gateway.go Outdated
return ctx.Err()
case <-timer.C:
func (g *gatewayImpl) doReconnect(ctx context.Context, state State, maximumAttempts int) error {
for attempt := 0; ; attempt++ {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of this for loop style
can we keep it a normal for one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I described use a plain for loop like before for {}

Comment thread voice/gateway.go Outdated
return 0
}
// Exponentially backoff up to a limit of 10s
exponent := min(attempt-1, 4)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the 4 doing here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was meant to limit the number of attempts, but it was quite meaningless since we already have maximumReconnectAttempts to limit the number of attempts. fixed it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you use a loop here now all of the sudden
there is no loop needed, just clamp it it goes over maximumConnectDelay

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. fixed it

Comment thread voice/conn.go Resolved
@kota113
kota113 commented Aug 16, 2026
Copy link
Copy Markdown
Contributor Author

@topi314 thank you for the comments. I reviewed your points and fixed them.

@topi314
topi314 merged commit 89b0237 into disgoorg:master Aug 25, 2026
3 of 4 checks passed
@kota113
kota113 deleted the fix/voice-reconnect-fresh-session branch August 25, 2026 22:29
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