Discord.js: VoiceChannel.join uncatchable exceptions on failure to join

Created on 20 Jan 2018  Â·  15Comments  Â·  Source: discordjs/discord.js

Please describe the problem you are having in as much detail as possible:

I have a voice channel recording bot. Recently I've noticed a few crashes because something has failed in VoiceChannel.join, but not gone to .catch (it just threw an exception that killed the whole process). It turns out that there is a class of errors joining the voice channel that throw uncatchable exceptions, ending the process entirely.

The problem is that the VoiceConnection emits an error before join's then has ever been called, so no opportunity is given to catch the error. (Further, an error is a bit excessive for failing to join a voice channel)

Include a reproducible code sample here, if possible:

const fs = require("fs");
const Discord = require("discord.js");
const config = JSON.parse(fs.readFileSync("config.json", "utf8"));

var client = new Discord.Client();

client.on("message", (msg) => {
    if (msg.content === "join" && msg.member && msg.member.voiceChannel) {
        msg.member.voiceChannel.join().then(()=>{
            msg.reply("Joined!");
        }).catch((ex)=>{
            msg.reply("Failed to join! " + ex);
        });
    }
});

client.on("voiceStateUpdate", (from, to) => {
    if (to.guild && to.guild.voiceConnection)
        to.guild.voiceConnection.disconnect();
});

client.login(config.token);

This example is obviously manufactured, but it was the only case I could find that will reliably crash. The crashes my bot is experiencing come from interactions that can't be reliably reproduced. My voiceStateUpdate function is obviously ridiculous here, and just poisons the join, but the point is that the error it causes cannot be caught, so the whole bot crashes.

Further details:

Output with above code:

(node:21871) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Tried to send join packet, but the WebSocket is not open.
(node:21871) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: Error: Connection not established within 15 seconds.
    at VoiceConnection.authenticateFailed (/home/yahweasel/craig-test/node_modules/discord.js/src/client/voice/VoiceConnection.js:258:26)
    at connectTimeout.client.setTimeout (/home/yahweasel/craig-test/node_modules/discord.js/src/client/voice/VoiceConnection.js:280:18)
    at Timeout.setTimeout (/home/yahweasel/craig-test/node_modules/discord.js/src/client/Client.js:422:7)
    at ontimeout (timers.js:475:11)
    at tryOnTimeout (timers.js:310:5)
    at Timer.listOnTimeout (timers.js:270:5)
  • discord.js version: 11.3.0
  • node.js version: 8.9.4
  • Operating system: Debian
  • Priority this issue should have – please be realistic and elaborate if possible: Fairly serious, as I have no idea how to avoid the bug in normal code.

  • [ ] I found this issue while running code on a __user account__
  • [ ] I have also tested the issue on latest master, commit hash:
won't fix error handling

Most helpful comment

Another reliable way to evoke such an error: Join a voice channel, crash the bot (ctrl+c or whatever's convenient), restart the bot and rejoin the channel quickly. It seems that if it's still within that weird nether time where the disconnected bot shows as online and in the channel, it will fail to connect and raise the uncatchable error.

All 15 comments

Note: Another situation in which this fairly reliably happens is if you join and then leave within 15 seconds, e.g. if a user requests a bot to join a channel and then either requests that it stops or kicks it from the server within 15 seconds. I could work around users requesting it stop too soon, but the fact that they can force it out means that any user can cause any discord.js bot with voice channel capability to crash simply by making it join and then kicking it out.

You can listen for this via client.on('error', console.error), if I am not mistaken. This should prevent your bot from crashing and enable you to handle it as you wish.

const fs = require("fs");
const Discord = require("discord.js");
const config = JSON.parse(fs.readFileSync("config.json", "utf8"));

var client = new Discord.Client();

client.on("message", (msg) => {
    if (msg.content === "join" && msg.member && msg.member.voiceChannel) {
        msg.member.voiceChannel.join().then(()=>{
            msg.reply("Joined!");
        }).catch((ex)=>{
            msg.reply("Failed to join! " + ex);
        });
        if (msg.guild.voiceChannel) {
            msg.guild.voiceChannel.on("error", (ex)=>{
                msg.reply("Failed to join (2)! " + ex);
            });
        } else {
            console.error("voiceChannel unavailable.");
        }
    }
});

client.on("voiceStateUpdate", (from, to) => {
    if (to.guild && to.guild.voiceConnection)
        to.guild.voiceConnection.disconnect();
});

client.on("error", (ex) => {
    console.error("ERROR " + ex);
});

client.login(config.token);

No change to behavior. Still crashes exactly the same.

The problem is that this error is emitted on the VoiceConnection, but the user hasn't yet been given a reference to the VoiceConnection (it's the return in join()), and it is not percolated up to anything useful (in particular the exception handler of the join() promise).

(I forgot I'd added a little if in there: That was just to test if I could actually sneak a reference to the voice connection early and add such an error handler. It's not available at that point, so it's a race condition to actually snag it, albeit a pretty leisurely race condition.)

Oh, secondary addition: On the day that I was having this error come up in my bot, I eventually discovered that the problem was the US Central voice server being flakey that day. All other voice servers (including all past voice servers for months of my running this bot) were fine and didn't invoke this crash. Thus, it's an error you wouldn't see in normal operation, which can come up with essentially no explanation. Even if there is a workaround to catch this exception, it's an enormous gotcha to bot writers that the workaround is not the natural way to get exceptions.

Verification: It is indeed possible to race to get the voice connection before it raises an error:

const fs = require("fs");
const Discord = require("discord.js");
const config = JSON.parse(fs.readFileSync("config.json", "utf8"));

var client = new Discord.Client();

client.on("message", (msg) => {
    if (msg.content === "join" && msg.member && msg.member.voiceChannel) {
        msg.member.voiceChannel.join().then(()=>{
            // Never reached
            msg.reply("Joined!");
        }).catch((ex)=>{
            // Never reached
            msg.reply("Failed to join! " + ex);
        });

        var insaneInterval = setInterval(()=>{
            if (msg.guild.voiceConnection) {
                msg.guild.voiceConnection.on("error", (ex)=>{
                    // Reached
                    msg.reply("Failed to join (2)! " + ex);
                });
                // This is an interval just in case it takes longer than 1 second for some reason
                clearInterval(insaneInterval);
            }
        }, 1000);
    }
});

client.on("voiceStateUpdate", (from, to) => {
    if (to.guild && to.guild.voiceConnection)
        to.guild.voiceConnection.disconnect();
});

client.on("error", (ex) => {
    // Never reached even without the interval above
    console.error("ERROR " + ex);
});

client.login(config.token);

So at least I can work around this without modifying discord.js. Still, I hope you agree this ain't great code...

Apologies for continued spamming of this bug report, but I'm just adding details as I discover them.

With the "fix" above in place to catch the error, things are still left in an inconsistent state. If you try to join the same voice channel again, join() resolves to a disconnected (!!!) voice channel. You have to join a different channel and then switch back to get it back into a usable state for any given guild. I tried explicitly disconnecting the errored voice connection and explicitly leaving the voice channel, but neither had the effect of allowing the next join to actually work. (In these tests of course I only poisoned the first join :) )

Another reliable way to evoke such an error: Join a voice channel, crash the bot (ctrl+c or whatever's convenient), restart the bot and rejoin the channel quickly. It seems that if it's still within that weird nether time where the disconnected bot shows as online and in the channel, it will fail to connect and raise the uncatchable error.

just use

process.on('SIGINT', () => <Client>.destroy());

to intercept ctrl-c and pm2 restart/reload/stop cases.

Literally could not be more irrelevant. Uncleanly disconnecting is not the bug. It is one reliable way to invoke the bug.

+1

I've had this annoying problem for a while now (consumer internet isn't completely stable) and the SIGINT solution seems to be the only way. That's not a good practice.. 👀

error event doesn't get emitted, it's one of those process-ending errors.

process.on("uncaughtException", (ex) => {
  // whatever
});

is a better solution, insofar as you can catch it and not kill the whole process, but there's nothing useful to do with the error at that point as it would be difficult to work your way back to the actual VoiceConnection.

My inane solution above of setting an interval to wait for the VoiceConnection to appear is actually perfectly reliable, as the uncatchable exception is thrown only after a preset delay. I've been using it in my bot, which establishes hundreds of voice connections a day, since I gave it here. That's the workaround to use until somebody involved in discord.js fixes it.

My attempts to reproduce this error on latest master using the code @Yahweasel provided failed, with the only uncaught error being one related to the websocket closing before being opened. I also attempted to join, stop bot, and join again as outlined. None of these methods reproduced this error, even with 500+ms network latency.

Just installed master. Same sample code as above.

$ node test4.js 
(node:8024) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Tried to send join packet, but the WebSocket is not open.
(node:8024) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error [VOICE_CONNECTION_TIMEOUT]: Connection not established within 15 seconds.
    at VoiceConnection.authenticateFailed (/home/yahweasel/craig-test/node_modules/discord.js/src/client/voice/VoiceConnection.js:254:26)
    at connectTimeout.client.setTimeout (/home/yahweasel/craig-test/node_modules/discord.js/src/client/voice/VoiceConnection.js:276:18)
    at Timeout.setTimeout (/home/yahweasel/craig-test/node_modules/discord.js/src/client/BaseClient.js:71:7)
    at ontimeout (timers.js:475:11)
    at tryOnTimeout (timers.js:310:5)
    at Timer.listOnTimeout (timers.js:270:5)

It takes 15 seconds to crash, of course, but it crashes exactly the same as release.

This issue has been pretty stale for a few months now, open a new issue if this still persists please.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Dmitry221060 picture Dmitry221060  Â·  3Comments

Dmitry221060 picture Dmitry221060  Â·  3Comments

kvn1351 picture kvn1351  Â·  3Comments

Alipoodle picture Alipoodle  Â·  3Comments

tiritto picture tiritto  Â·  3Comments