Gateway: Add connection timeout, add Config to gateway. (#51)

This change fixes tasks hanging due to rare cases of messages being lost between full Discord reconnections by placing a configurable timeout on the `ConnectionInfo` responses. This is a companion fix to [serenity#1255](https://github.com/serenity-rs/serenity/pull/1255). To make this doable, `Config`s are now used by all versions of `Songbird`/`Call`, and relevant functions are  added to simplify setup with configuration. These are now non-exhaustive, correcting an earlier oversight. For future extensibility, this PR moves the return type of `join`/`join_gateway` into a custom future (no longer leaking flume's `RecvFut` type).

Additionally, this fixes the Makefile's feature sets for driver/gateway-only compilation.

This is a breaking change in:
* the return types of `join`/`join_gateway`
* moving `crate::driver::Config` -> `crate::Config`,
* `Config` and `JoinError` becoming `#[non_breaking]`.

This was tested via `cargo make ready`, and by testing `examples/serenity/voice_receive` with various timeout settings.
This commit is contained in:
Kyle Simpson
2021-03-29 19:51:13 +01:00
parent f449d4f679
commit 1fc3dc2259
18 changed files with 426 additions and 119 deletions

View File

@@ -1,15 +1,14 @@
#[cfg(feature = "driver-core")]
use crate::{
driver::{Config, Driver},
error::ConnectionResult,
};
use crate::{driver::Driver, error::ConnectionResult};
use crate::{
error::{JoinError, JoinResult},
id::{ChannelId, GuildId, UserId},
info::{ConnectionInfo, ConnectionProgress},
join::*,
shards::Shard,
Config,
};
use flume::{r#async::RecvFut, Sender};
use flume::Sender;
use serde_json::json;
use tracing::instrument;
@@ -18,9 +17,15 @@ use std::ops::{Deref, DerefMut};
#[derive(Clone, Debug)]
enum Return {
// Return the connection info as it is received.
Info(Sender<ConnectionInfo>),
// Two channels: first indicates "gateway connection" was successful,
// second indicates that the driver successfully connected.
// The first is needed to cancel a timeout as the driver can/should
// have separate connection timing/retry config.
#[cfg(feature = "driver-core")]
Conn(Sender<ConnectionResult<()>>),
Conn(Sender<()>, Sender<ConnectionResult<()>>),
}
/// The Call handler is responsible for a single voice connection, acting
@@ -32,6 +37,9 @@ enum Return {
/// [`Driver`]: struct@Driver
#[derive(Clone, Debug)]
pub struct Call {
#[cfg(not(feature = "driver-core"))]
config: Config,
connection: Option<(ConnectionProgress, Return)>,
#[cfg(feature = "driver-core")]
@@ -61,19 +69,13 @@ impl Call {
#[inline]
#[instrument]
pub fn new(guild_id: GuildId, ws: Shard, user_id: UserId) -> Self {
Self::new_raw(guild_id, Some(ws), user_id)
Self::new_raw_cfg(guild_id, Some(ws), user_id, Default::default())
}
#[cfg(feature = "driver-core")]
/// Creates a new Call, configuring the driver as specified.
#[inline]
#[instrument]
pub fn from_driver_config(
guild_id: GuildId,
ws: Shard,
user_id: UserId,
config: Config,
) -> Self {
pub fn from_config(guild_id: GuildId, ws: Shard, user_id: UserId, config: Config) -> Self {
Self::new_raw_cfg(guild_id, Some(ws), user_id, config)
}
@@ -88,38 +90,22 @@ impl Call {
#[inline]
#[instrument]
pub fn standalone(guild_id: GuildId, user_id: UserId) -> Self {
Self::new_raw(guild_id, None, user_id)
Self::new_raw_cfg(guild_id, None, user_id, Default::default())
}
#[cfg(feature = "driver-core")]
/// Creates a new standalone Call, configuring the driver as specified.
/// Creates a new standalone Call from the given configuration file.
#[inline]
#[instrument]
pub fn standalone_from_driver_config(
guild_id: GuildId,
user_id: UserId,
config: Config,
) -> Self {
pub fn standalone_from_config(guild_id: GuildId, user_id: UserId, config: Config) -> Self {
Self::new_raw_cfg(guild_id, None, user_id, config)
}
fn new_raw(guild_id: GuildId, ws: Option<Shard>, user_id: UserId) -> Self {
Call {
connection: None,
#[cfg(feature = "driver-core")]
driver: Default::default(),
guild_id,
self_deaf: false,
self_mute: false,
user_id,
ws,
}
}
#[cfg(feature = "driver-core")]
fn new_raw_cfg(guild_id: GuildId, ws: Option<Shard>, user_id: UserId, config: Config) -> Self {
Call {
#[cfg(not(feature = "driver-core"))]
config,
connection: None,
#[cfg(feature = "driver-core")]
driver: Driver::new(config),
guild_id,
self_deaf: false,
@@ -137,8 +123,11 @@ impl Call {
let _ = tx.send(c.clone());
},
#[cfg(feature = "driver-core")]
Some((ConnectionProgress::Complete(c), Return::Conn(tx))) => {
self.driver.raw_connect(c.clone(), tx.clone());
Some((ConnectionProgress::Complete(c), Return::Conn(first_tx, driver_tx))) => {
// It's okay if the receiver hung up.
let _ = first_tx.send(());
self.driver.raw_connect(c.clone(), driver_tx.clone());
},
_ => {},
}
@@ -209,11 +198,9 @@ impl Call {
///
/// [`Songbird::join`]: crate::Songbird::join
#[instrument(skip(self))]
pub async fn join(
&mut self,
channel_id: ChannelId,
) -> JoinResult<RecvFut<'static, ConnectionResult<()>>> {
pub async fn join(&mut self, channel_id: ChannelId) -> JoinResult<Join> {
let (tx, rx) = flume::unbounded();
let (gw_tx, gw_rx) = flume::unbounded();
let do_conn = self
.should_actually_join(|_| Ok(()), &tx, channel_id)
@@ -222,12 +209,20 @@ impl Call {
if do_conn {
self.connection = Some((
ConnectionProgress::new(self.guild_id, self.user_id, channel_id),
Return::Conn(tx),
Return::Conn(gw_tx, tx),
));
self.update().await.map(|_| rx.into_recv_async())
let timeout = self.config().gateway_timeout;
self.update()
.await
.map(|_| Join::new(rx.into_recv_async(), gw_rx.into_recv_async(), timeout))
} else {
Ok(rx.into_recv_async())
Ok(Join::new(
rx.into_recv_async(),
gw_rx.into_recv_async(),
None,
))
}
}
@@ -247,10 +242,7 @@ impl Call {
///
/// [`Songbird::join_gateway`]: crate::Songbird::join_gateway
#[instrument(skip(self))]
pub async fn join_gateway(
&mut self,
channel_id: ChannelId,
) -> JoinResult<RecvFut<'static, ConnectionInfo>> {
pub async fn join_gateway(&mut self, channel_id: ChannelId) -> JoinResult<JoinGateway> {
let (tx, rx) = flume::unbounded();
let do_conn = self
@@ -267,9 +259,13 @@ impl Call {
Return::Info(tx),
));
self.update().await.map(|_| rx.into_recv_async())
let timeout = self.config().gateway_timeout;
self.update()
.await
.map(|_| JoinGateway::new(rx.into_recv_async(), timeout))
} else {
Ok(rx.into_recv_async())
Ok(JoinGateway::new(rx.into_recv_async(), None))
}
}
@@ -414,6 +410,24 @@ impl Call {
}
}
#[cfg(not(feature = "driver-core"))]
impl Call {
/// Access this call handler's configuration.
pub fn config(&self) -> &Config {
&self.config
}
/// Mutably access this call handler's configuration.
pub fn config_mut(&mut self) -> &mut Config {
&mut self.config
}
/// Set this call handler's configuration.
pub fn set_config(&mut self, config: Config) {
self.config = config;
}
}
#[cfg(feature = "driver-core")]
impl Deref for Call {
type Target = Driver;