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:
116
src/handler.rs
116
src/handler.rs
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user