Skip to content

Commit 8293247

Browse files
authored
Attempt a full reconnection when a resume attempt failed. (#1465)
lgtm ship it!
1 parent 138acb3 commit 8293247

File tree

9 files changed

+94
-51
lines changed

9 files changed

+94
-51
lines changed

include/dpp/discordclient.h

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,33 @@ enum shard_frame_type : int {
139139
ft_request_soundboard_sounds = 31,
140140
};
141141

142+
/**
143+
* @brief Callback to send a Gateway request to start a new voice connection.
144+
*/
145+
using voice_connection_gateway_request_callback_t = std::function<void(discord_client*)>;
146+
142147
/**
143148
* @brief Represents a connection to a voice channel.
144149
* A client can only connect to one voice channel per guild at a time, so these are stored in a map
145150
* in the dpp::discord_client keyed by guild_id.
146151
*/
147-
class DPP_EXPORT voiceconn {
152+
class DPP_EXPORT voiceconn : public std::enable_shared_from_this<voiceconn> {
148153
/**
149154
* @brief Owning dpp::discord_client instance
150155
*/
151156
class discord_client* creator;
157+
158+
/**
159+
* @brief Function to ask a discord_client instance to make a request to Gateway to connect to a voice channel
160+
*/
161+
voice_connection_gateway_request_callback_t request_callback;
162+
152163
public:
164+
/**
165+
* @brief Guild to connect to the voice channel on
166+
*/
167+
snowflake guild_id;
168+
153169
/**
154170
* @brief Voice Channel ID
155171
*/
@@ -173,28 +189,25 @@ class DPP_EXPORT voiceconn {
173189
/**
174190
* @brief voice websocket client
175191
*/
176-
class discord_voice_client* voiceclient;
192+
std::unique_ptr<class discord_voice_client> voiceclient;
177193

178194
/**
179195
* @brief True to enable DAVE E2EE
180196
* @warning This is an EXPERIMENTAL feature!
181197
*/
182198
bool dave;
183199

184-
/**
185-
* @brief Construct a new voiceconn object
186-
*/
187-
voiceconn() = default;
188-
189200
/**
190201
* @brief Construct a new voiceconn object
191202
*
192203
* @param o owner
193-
* @param _channel_id voice channel id
204+
* @param request_callback Function to ask a discord_client instance to make a request to Gateway to connect to a voice channel
205+
* @param guild_id Guild to connect to the voice channel on
206+
* @param channel_id voice channel id
194207
* @param enable_dave True to enable DAVE E2EE
195208
* @warn DAVE is an EXPERIMENTAL feature!
196209
*/
197-
voiceconn(class discord_client* o, snowflake _channel_id, bool enable_dave);
210+
voiceconn(class discord_client* o, voice_connection_gateway_request_callback_t request_callback, snowflake guild_id, snowflake channel_id, bool enable_dave);
198211

199212
/**
200213
* @brief Destroy the voiceconn object
@@ -216,16 +229,17 @@ class DPP_EXPORT voiceconn {
216229
*/
217230
bool is_active() const;
218231

232+
voiceconn& request();
233+
219234
/**
220235
* @brief Create websocket object and connect it.
221236
* Needs hostname, token and session_id to be set or does nothing.
222-
*
223-
* @param guild_id Guild to connect to the voice channel on
237+
*
224238
* @return reference to self
225239
* @note It can spawn a thread to establish the connection, so this is NOT a synchronous blocking call!
226240
* You shouldn't call this directly. Use a wrapper function instead. e.g. dpp::guild::connect_member_voice
227241
*/
228-
voiceconn& connect(snowflake guild_id);
242+
voiceconn& connect();
229243

230244
/**
231245
* @brief Disconnect from the currently connected voice channel
@@ -436,7 +450,7 @@ class DPP_EXPORT discord_client : public websocket_client
436450
/**
437451
* @brief List of voice channels we are connecting to keyed by guild id
438452
*/
439-
std::unordered_map<snowflake, std::unique_ptr<voiceconn>> connecting_voice_channels;
453+
std::unordered_map<snowflake, std::shared_ptr<voiceconn>> connecting_voice_channels;
440454

441455
/**
442456
* @brief The gateway address we reconnect to when we resume a session

include/dpp/discordvoiceclient.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ struct dave_binary_header_t {
230230
*/
231231
using privacy_code_callback_t = std::function<void(const std::string&)>;
232232

233+
/**
234+
* @brief A callback for a full reconnection after the voice client disconnects due to error.
235+
*/
236+
using full_reconnection_callback_t = std::function<void()>;
237+
233238
/** @brief Implements a discord voice connection.
234239
* Each discord_voice_client connects to one voice channel and derives from a websocket client.
235240
*/
@@ -709,6 +714,8 @@ class DPP_EXPORT discord_voice_client : public websocket_client
709714
*/
710715
class dpp::cluster* creator{};
711716

717+
full_reconnection_callback_t reconnection_callback;
718+
712719
/**
713720
* @brief True when the thread is shutting down
714721
*/
@@ -872,6 +879,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client
872879

873880
/** Constructor takes shard id, max shards and token.
874881
* @param _cluster The cluster which owns this voice connection, for related logging, REST requests etc
882+
* @param _reconnection_callback The callback this voice client calls if the voice connection was closed due to error
875883
* @param _channel_id The channel id to identify the voice connection as
876884
* @param _server_id The server id (guild id) to identify the voice connection as
877885
* @param _token The voice session token to use for identifying to the websocket
@@ -881,7 +889,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client
881889
* @throw dpp::voice_exception Opus failed to initialise, or D++ is not compiled with voice support
882890
* @warning DAVE E2EE is an EXPERIMENTAL feature!
883891
*/
884-
discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave = false);
892+
discord_voice_client(dpp::cluster* _cluster, full_reconnection_callback_t _reconnection_callback, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave = false);
885893

886894
/**
887895
* @brief Destroy the discord voice client object

src/dpp/discordclient.cpp

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -514,21 +514,24 @@ discord_client& discord_client::connect_voice(snowflake guild_id, snowflake chan
514514
return *this;
515515
}
516516
}
517-
connecting_voice_channels[guild_id] = std::make_unique<voiceconn>(this, channel_id, enable_dave);
518-
/* Once sent, this expects two events (in any order) on the websocket:
519-
* VOICE_SERVER_UPDATE and VOICE_STATUS_UPDATE
520-
*/
521-
log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id) + (enable_dave ? " WITH DAVE" : ""));
522-
queue_message(jsonobj_to_string(json({
523-
{ "op", ft_voice_state_update },
524-
{ "d", {
525-
{ "guild_id", std::to_string(guild_id) },
526-
{ "channel_id", std::to_string(channel_id) },
527-
{ "self_mute", self_mute },
528-
{ "self_deaf", self_deaf },
517+
connecting_voice_channels[guild_id] = std::make_shared<voiceconn>(this, [guild_id, channel_id, self_mute, self_deaf, enable_dave](discord_client* client) {
518+
/* Once sent, this expects two events (in any order) on the websocket:
519+
* VOICE_SERVER_UPDATE and VOICE_STATUS_UPDATE
520+
*/
521+
// Always honor the given client rather than this because
522+
// the owner can change after shard reconnection.
523+
client->log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id) + (enable_dave ? " WITH DAVE" : ""));
524+
client->queue_message(client->jsonobj_to_string(json({
525+
{ "op", ft_voice_state_update },
526+
{ "d", {
527+
{ "guild_id", std::to_string(guild_id) },
528+
{ "channel_id", std::to_string(channel_id) },
529+
{ "self_mute", self_mute },
530+
{ "self_deaf", self_deaf },
531+
}
529532
}
530-
}
531-
})), false);
533+
})), false);}, guild_id, channel_id, enable_dave);
534+
532535
#endif
533536
return *this;
534537
}
@@ -581,7 +584,8 @@ voiceconn* discord_client::get_voice(snowflake guild_id) {
581584
}
582585

583586

584-
voiceconn::voiceconn(discord_client* o, snowflake _channel_id, bool enable_dave) : creator(o), channel_id(_channel_id), voiceclient(nullptr), dave(enable_dave) {
587+
voiceconn::voiceconn(discord_client* o, voice_connection_gateway_request_callback_t request_callback, snowflake guild_id, snowflake channel_id, bool enable_dave) : creator(o), request_callback(std::move(request_callback)), guild_id(guild_id), channel_id(channel_id), voiceclient(nullptr), dave(enable_dave) {
588+
request();
585589
}
586590

587591
bool voiceconn::is_ready() const {
@@ -594,8 +598,7 @@ bool voiceconn::is_active() const {
594598

595599
voiceconn& voiceconn::disconnect() {
596600
if (this->is_active()) {
597-
delete voiceclient;
598-
voiceclient = nullptr;
601+
voiceclient.reset();
599602
}
600603
return *this;
601604
}
@@ -604,16 +607,31 @@ voiceconn::~voiceconn() {
604607
this->disconnect();
605608
}
606609

607-
voiceconn& voiceconn::connect(snowflake guild_id) {
610+
voiceconn& voiceconn::request() {
611+
this->token.clear();
612+
this->session_id.clear();
613+
this->websocket_hostname.clear();
614+
this->voiceclient.reset();
615+
616+
this->request_callback(this->creator);
617+
return *this;
618+
}
619+
620+
voiceconn& voiceconn::connect() {
608621
if (this->is_ready() && !this->is_active()) {
609622
try {
610-
this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(guild_id) + " channel " + std::to_string(this->channel_id));
611-
this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname, this->dave);
623+
this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(this->guild_id) + " channel " + std::to_string(this->channel_id));
624+
full_reconnection_callback_t reconnection_callback = [weak_this=weak_from_this()] {
625+
if (std::shared_ptr<voiceconn> strong_this = weak_this.lock()) {
626+
strong_this->request();
627+
}
628+
};
629+
this->voiceclient = std::make_unique<discord_voice_client>(creator->creator, std::move(reconnection_callback), this->channel_id, this->guild_id, this->token, this->session_id, this->websocket_hostname, this->dave);
612630
/* Note: Spawns thread! */
613631
this->voiceclient->run();
614632
}
615633
catch (std::exception &e) {
616-
this->creator->log(ll_debug, "Can't connect to voice websocket (guild_id: " + std::to_string(guild_id) + ", channel_id: " + std::to_string(this->channel_id) + "): " + std::string(e.what()));
634+
this->creator->log(ll_debug, "Can't connect to voice websocket (guild_id: " + std::to_string(this->guild_id) + ", channel_id: " + std::to_string(this->channel_id) + "): " + std::string(e.what()));
617635
}
618636
}
619637
return *this;

src/dpp/events/voice_server_update.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@ void voice_server_update::handle(discord_client* client, json &j, const std::str
4949
auto v = client->connecting_voice_channels.find(vsu.guild_id);
5050
/* Check to see if there is a connection in progress for a voice channel on this guild */
5151
if (v != client->connecting_voice_channels.end()) {
52-
if (!v->second->is_ready()) {
53-
v->second->token = vsu.token;
54-
v->second->websocket_hostname = vsu.endpoint;
55-
if (!v->second->is_active()) {
56-
v->second->connect(vsu.guild_id);
52+
voiceconn& connection = *v->second;
53+
if (!connection.is_ready()) {
54+
connection.token = vsu.token;
55+
connection.websocket_hostname = vsu.endpoint;
56+
if (!connection.is_active()) {
57+
connection.connect();
5758
}
5859
}
5960
}
@@ -66,4 +67,4 @@ void voice_server_update::handle(discord_client* client, json &j, const std::str
6667
}
6768
}
6869

69-
};
70+
};

src/dpp/events/voice_state_update.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ void voice_state_update::handle(discord_client* client, json &j, const std::stri
7575
auto v = client->connecting_voice_channels.find(vsu.state.guild_id);
7676
/* Check to see if we have a connection to a voice channel in progress on this guild */
7777
if (v != client->connecting_voice_channels.end()) {
78-
v->second->session_id = vsu.state.session_id;
79-
if (v->second->is_ready() && !v->second->is_active()) {
80-
v->second->connect(vsu.state.guild_id);
78+
voiceconn& connection = *v->second;
79+
connection.session_id = vsu.state.session_id;
80+
if (connection.is_ready() && !connection.is_active()) {
81+
connection.connect();
8182
}
8283
}
8384
}

src/dpp/voice/enabled/constructor.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
namespace dpp {
3434

35-
discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave)
35+
discord_voice_client::discord_voice_client(dpp::cluster* _cluster, full_reconnection_callback_t _reconnection_callback, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave)
3636
: websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT),
3737
connect_time(0),
3838
mixer(std::make_unique<audio_mixer>()),
@@ -54,6 +54,7 @@ discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _ch
5454
tracks(0),
5555
dave_version(enable_dave ? dave_version_1 : dave_version_none),
5656
creator(_cluster),
57+
reconnection_callback(std::move(_reconnection_callback)),
5758
terminating(false),
5859
heartbeat_interval(0),
5960
last_heartbeat(time(nullptr)),

src/dpp/voice/enabled/handle_frame.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,6 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
343343
this->heartbeat_interval = j["d"]["heartbeat_interval"].get<uint32_t>();
344344
}
345345

346-
/* Reset receive_sequence on HELLO */
347-
receive_sequence = -1;
348-
349346
if (!modes.empty()) {
350347
log(dpp::ll_debug, "Resuming voice session " + this->sessionid + "...");
351348
json obj = {
@@ -362,6 +359,8 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
362359
};
363360
this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT);
364361
} else {
362+
/* Reset receive_sequence on HELLO */
363+
receive_sequence = -1;
365364
log(dpp::ll_debug, "Connecting new voice session (DAVE: " + std::string(dave_version == dave_version_1 ? "Enabled" : "Disabled") + ")...");
366365
json obj = {
367366
{ "op", voice_opcode_connection_identify },

src/dpp/voice/enabled/thread.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ void discord_voice_client::on_disconnect() {
4545

4646
/* If we've looped 5 or more times, abort the loop. */
4747
if (terminating || times_looped >= 5) {
48-
log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket.");
48+
log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Starting a full reconnection.");
49+
owner->queue_work(1, reconnection_callback);
4950
return;
5051
}
5152
last_loop_time = current_time;
@@ -72,4 +73,4 @@ void discord_voice_client::run() {
7273
ssl_connection::read_loop();
7374
}
7475

75-
}
76+
}

src/dpp/voice/stub/stubs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
namespace dpp {
2929

30-
discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave)
30+
discord_voice_client::discord_voice_client(dpp::cluster* _cluster, full_reconnection_callback_t _reconnect_callback, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave)
3131
: websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT)
3232
{
3333
throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++");

0 commit comments

Comments
 (0)