discordgo icon indicating copy to clipboard operation
discordgo copied to clipboard

Voice websocket closed unexpectedly at random times

Open yms2772 opened this issue 4 years ago • 7 comments

I'm making a bot which is playing songs using github.com/jonas747/dca. However it always leave the voice channel at random times (about after 1 hour) with below log and join the channel again. My internet is stable and I even used Amazon AWS but it showed same problem.

2020/10/31 15:14:36 [DG0] voice.go:376:wsListen() voice endpoint south-korea889.discord.media:443 websocket closed unexpectantly, websocket: close 1001 (going away)
2020/10/31 15:14:36 [DG0] voice.go:197:Close() error closing websocket, websocket: close sent
2020/10/31 15:14:37 [DG0] voice.go:206:Close() error closing websocket, write tcp 172.17.0.8:60728->162.159.128.235:443: i/o timeout
2020/10/31 15:14:49 [DG1] wsapi.go:648:ChannelVoiceJoin() error waiting for voice to connect, timeout waiting for voice

yms2772 avatar Oct 31 '20 06:10 yms2772

I've been getting this and it's a real pain. No way to catch it or anything. For me at least, it doesn't always crash from it; only about half the time. Really hoping someone will give us an update. Even a basic recover() would be a godsend at this point

colecrouter avatar Nov 10 '20 03:11 colecrouter

I have an update on this one. Thanks to the @FlameInTheDark on this issue here, it appears that using this:

vc, err := discord.ChannelVoiceJoin(guildID, channelID, properties.Muted, properties.Deafened)
	if err != nil {
		if _, ok := discord.VoiceConnections[guildID]; ok {
			vc = discord.VoiceConnections[guildID]
		} else {
			return nil, err
		}
	}

—will fix your issue.

It almost looks like (Session*) ChannelVoiceJoin() just doesn't return properly sometimes. Not sure if this is a discordgo issue, or a Discord issue.

colecrouter avatar Nov 18 '20 04:11 colecrouter

I have an update on this one. Thanks to the @FlameInTheDark on this issue here, it appears that using this:

vc, err := discord.ChannelVoiceJoin(guildID, channelID, properties.Muted, properties.Deafened)
	if err != nil {
		if _, ok := discord.VoiceConnections[guildID]; ok {
			vc = discord.VoiceConnections[guildID]
		} else {
			return nil, err
		}
	}

—will fix your issue.

It almost looks like (Session*) ChannelVoiceJoin() just doesn't return properly sometimes. Not sure if this is a discordgo issue, or a Discord issue.

I'm trying using that code and it does not timeout even after an hour has passed. Thank you for your reply.

yms2772 avatar Nov 18 '20 04:11 yms2772

This doesn't seem to be working for me anymore. I haven't had a chance to double check and make sure I didn't screw something up, but I will as soon as I can. EDIT: it appears that the uppermost err is still getting returned. I'm going to try adding another:

if err != nil {
	log.Fatalln("error connecting:", err)
	return // Emphasis on the return
}

—at the end. This doesn't look like it'll fix the issue, but it might stop the crashing? We'll see.

colecrouter avatar Nov 24 '20 02:11 colecrouter

This doesn't seem to be working for me anymore. I haven't had a chance to double check and make sure I didn't screw something up, but I will as soon as I can. EDIT: it appears that the uppermost err is still getting returned. I'm going to try adding another:

if err != nil {
	log.Fatalln("error connecting:", err)
	return // Emphasis on the return
}

—at the end. This doesn't look like it'll fix the issue, but it might stop the crashing? We'll see.

I've been thinking about this a lot, I think I should give the websocket a keepalive signal like below code.

func DiscordVoicePing(guildID string) {
	voiceConnection[guildID].StreamSession.SetPaused(true)
	done := make(chan error)

	encodingSession, _ := dca.EncodeFile("./bin/ping.mp3", options)
	dca.NewStream(encodingSession, voiceConnection[guildID].VC, done)

	err := <-done
	if err != nil && err != io.EOF {
		log.Printf("Ping Error: %s", err.Error())
	}

	voiceConnection[guildID].StreamSession.SetPaused(false)
}

ping.mp3 is a 0.3 second silent file. Since that function is executed once every 10 minutes with a ticker, the voice does not stop.

yms2772 avatar Dec 06 '20 06:12 yms2772

Idiot me didn't realize that we were using log.Fatalln() and not log.Println() so that's definitely contributing to the exiting. Like I said, this won't solve the crashing outright, but "failing to function consistently" is miles better than crashing. Here's what I have now:

vc, err = s.ChannelVoiceJoin(e.GuildID, e.ChannelID, false, false)
if err != nil {
	if err, ok := s.VoiceConnections[e.GuildID]; ok {
		vc = s.VoiceConnections[e.GuildID]
		if err != nil {
			log.Println("error connecting:", err)
			return
		}
	} else {
		log.Println("error connecting:", err)
		return
	}
}

Will report back later. For anyone else who's interested, this is the line with the function that throws the error. Looks like just a simple loop waiting for it to Ready. Either Ready isn't being set properly, or it's an issue on Discord's end. That's my naïve take at least.

colecrouter avatar Dec 06 '20 22:12 colecrouter

Looks like the snippet I posted above does the trick. However, the error is still happening. This is what I've found:

i := 0
for {
	if v.sessionID != "" {
		break
	}
	if i > 20 { // only loop for up to 1 second total
		return fmt.Errorf("did not receive voice Session ID in time")
	}
	time.Sleep(50 * time.Millisecond)
	i++
}

The whole error down the chain being:

[DG0] wsapi.go:744:onVoiceServerUpdate() onVoiceServerUpdate voice.open, did not receive voice Session ID in time

This is what I've found from the Discord docs:

f our request succeeded, the gateway will respond with two events—a Voice State Update event and a Voice Server Update event—meaning your library must properly wait for both events before continuing. The first will contain a new key, session_id, and the second will provide voice server information we can use to establish a new voice connection:

So the bot is waiting on two packets, and it kinda looks like they're not arriving. Granted, it is only waiting 1 second. While the waitUntilConnected() function tries for 10 seconds, it won't happen because open() has already returned an error and is no longer listening (if I understand it correctly). I think this here might be our problem.

I'll try to fork it, make the change, and test it when I get the chance. In the mean time, anyone is welcome to try increasing the timeout from the line I mentioned above and reporting back.

colecrouter avatar Dec 08 '20 01:12 colecrouter

I'll try to fork it, make the change, and test it when I get the chance. In the mean time, anyone is welcome to try increasing the timeout from the line I mentioned above and reporting back.

I had the same bug, but increasing the timeout had no effect. I realized it's a deadlock; wsapi.go locks before populating the sessionID we need, but the open() function holds the lock for the entire function, meaning the sessionID can never be updated in that sleep loop.

See my PR mentioned above for the fix.

denverquane avatar Jul 17 '23 16:07 denverquane