reconnection wip + simulate scenario + fixed cyclic types

This commit is contained in:
Théo Monnom
2022-12-22 23:07:29 +01:00
parent c1cba8dcbd
commit 9c489d51f4
6 changed files with 242 additions and 78 deletions
+9 -1
View File
@@ -17,9 +17,13 @@ use crate::proto::participant_info;
use thiserror::Error; use thiserror::Error;
use tracing::{debug, error, instrument, trace_span, Level}; use tracing::{debug, error, instrument, trace_span, Level};
use crate::rtc_engine::{EngineError, EngineEvent, EngineEvents, RTCEngine}; use crate::rtc_engine::{
EngineError, EngineEvent, EngineEvents, EngineResult, RTCEngine,
};
use crate::signal_client::SignalOptions; use crate::signal_client::SignalOptions;
pub use crate::rtc_engine::SimulateScenario;
pub mod id; pub mod id;
pub mod participant; pub mod participant;
pub mod publication; pub mod publication;
@@ -375,6 +379,10 @@ impl RoomHandle {
pub fn local_participant(&self) -> Arc<LocalParticipant> { pub fn local_participant(&self) -> Arc<LocalParticipant> {
self.inner.local_participant.clone() self.inner.local_participant.clone()
} }
pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> {
self.inner.rtc_engine.simulate_scenario(scenario).await
}
} }
fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> { fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> {
+95 -54
View File
@@ -1,3 +1,4 @@
use futures::future::BoxFuture;
use futures::FutureExt; use futures::FutureExt;
use livekit_webrtc::data_channel::DataSendError; use livekit_webrtc::data_channel::DataSendError;
use livekit_webrtc::jsep::SdpParseError; use livekit_webrtc::jsep::SdpParseError;
@@ -31,6 +32,18 @@ pub(crate) type EngineEmitter = mpsc::Sender<EngineEvent>;
pub(crate) type EngineEvents = mpsc::Receiver<EngineEvent>; pub(crate) type EngineEvents = mpsc::Receiver<EngineEvent>;
pub(crate) type EngineResult<T> = Result<T, EngineError>; pub(crate) type EngineResult<T> = Result<T, EngineError>;
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(u8)]
pub enum SimulateScenario {
SignalReconnect,
Speaker,
NodeFailure,
ServerLeave,
Migration,
ForceTcp,
ForceTls,
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum EngineError { pub enum EngineError {
#[error("signal failure: {0}")] #[error("signal failure: {0}")]
@@ -74,19 +87,7 @@ lazy_static! {
// Share one LKRuntime across all RTCEngine instances // Share one LKRuntime across all RTCEngine instances
static ref LK_RUNTIME: Mutex<Weak<LKRuntime>> = Mutex::new(Weak::new()); static ref LK_RUNTIME: Mutex<Weak<LKRuntime>> = Mutex::new(Weak::new());
} }
///
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(u8)]
pub enum SimulateScenario {
SignalReconnect,
Speaker,
NodeFailure,
ServerLeave,
Migration,
ForceTcp,
ForceTls,
}
/// Represents a running RTCSession with the ability to close the session /// Represents a running RTCSession with the ability to close the session
/// and the engine_task /// and the engine_task
#[derive(Debug)] #[derive(Debug)]
@@ -150,13 +151,7 @@ impl RTCEngine {
#[tracing::instrument] #[tracing::instrument]
pub async fn close(&self) { pub async fn close(&self) {
self.inner.opened.store(false, Ordering::SeqCst); self.inner.close().await
self.inner.close();
let _ = self
.inner
.engine_emitter
.send(EngineEvent::Disconnected)
.await;
} }
#[tracing::instrument(skip(data))] #[tracing::instrument(skip(data))]
@@ -164,7 +159,7 @@ impl RTCEngine {
&self, &self,
data: &DataPacket, data: &DataPacket,
kind: data_packet::Kind, kind: data_packet::Kind,
) -> Result<(), EngineError> { ) -> EngineResult<()> {
self.inner.wait_reconnection().await?; self.inner.wait_reconnection().await?;
self.inner self.inner
.running_handle .running_handle
@@ -174,8 +169,20 @@ impl RTCEngine {
.unwrap() .unwrap()
.session .session
.publish_data(data, kind) .publish_data(data, kind)
.await?; .await
}
pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> {
self.inner.wait_reconnection().await?;
self.inner
.running_handle
.read()
.await
.as_ref()
.unwrap()
.session
.simulate_scenario(scenario)
.await;
Ok(()) Ok(())
} }
@@ -207,16 +214,24 @@ impl EngineInner {
}, },
_ = &mut close_receiver => { _ = &mut close_receiver => {
break; break;
}
} }
} }
} }
async fn on_session_event(self: &Arc<Self>, event: SessionEvent) -> EngineResult<()> { async fn on_session_event(self: &Arc<Self>, event: SessionEvent) -> EngineResult<()> {
match event { match event {
SessionEvent::Close { reason } => { SessionEvent::Close {
info!("received session close: {}", reason); source,
self.handle_disconnected().await; reason,
can_reconnect,
} => {
info!("received session close: {}, {:?}", source, reason);
if can_reconnect {
self.handle_disconnected().await;
} else {
self.close().await;
}
} }
SessionEvent::Data { data } => {} SessionEvent::Data { data } => {}
SessionEvent::MediaTrack { SessionEvent::MediaTrack {
@@ -238,38 +253,41 @@ impl EngineInner {
Ok(()) Ok(())
} }
async fn connect( fn connect<'a>(
self: &Arc<Self>, self: &'a Arc<Self>,
url: &str, url: &'a str,
token: &str, token: &'a str,
options: SignalOptions, options: SignalOptions,
) -> EngineResult<()> { ) -> BoxFuture<'a, EngineResult<()>> {
let (session_emitter, session_events) = mpsc::unbounded_channel(); async {
let session = RTCSession::connect( let (session_emitter, session_events) = mpsc::unbounded_channel();
url, let session = RTCSession::connect(
token, url,
options, token,
self.lk_runtime.clone(), options,
session_emitter, self.lk_runtime.clone(),
) session_emitter,
.await?; )
.await?;
let (close_sender, close_receiver) = oneshot::channel(); let (close_sender, close_receiver) = oneshot::channel();
let engine_task = tokio::spawn(self.clone().engine_task(session_events, close_receiver)); let engine_task =
tokio::spawn(self.clone().engine_task(session_events, close_receiver));
*self.session_info.lock() = Some(session.info().clone()); *self.session_info.lock() = Some(session.info().clone());
*self.running_handle.write().await = Some(EngineHandle { *self.running_handle.write().await = Some(EngineHandle {
session, session,
engine_task, engine_task,
close_sender, close_sender,
}); });
self.opened.store(true, Ordering::SeqCst); self.opened.store(true, Ordering::SeqCst);
Ok(())
Ok(()) }
.boxed()
} }
async fn close(&self) { async fn terminate_session(&self) {
if let Some(handle) = self.running_handle.write().await.take() { if let Some(handle) = self.running_handle.write().await.take() {
handle.session.close().await; handle.session.close().await;
let _ = handle.close_sender.send(()); let _ = handle.close_sender.send(());
@@ -277,6 +295,12 @@ impl EngineInner {
} }
} }
async fn close(&self) {
self.opened.store(false, Ordering::SeqCst);
self.terminate_session().await;
let _ = self.engine_emitter.send(EngineEvent::Disconnected).await;
}
async fn wait_reconnection(&self) -> EngineResult<()> { async fn wait_reconnection(&self) -> EngineResult<()> {
if !self.opened.load(Ordering::SeqCst) { if !self.opened.load(Ordering::SeqCst) {
Err(EngineError::Connection("not opened".to_owned()))? Err(EngineError::Connection("not opened".to_owned()))?
@@ -293,6 +317,23 @@ impl EngineInner {
Ok(()) Ok(())
} }
fn try_reconnect(self: Arc<Self>) {
warn!("reconnecting RTCEngine...");
if !self.opened.load(Ordering::SeqCst) || self.reconnecting.load(Ordering::SeqCst) {
return;
}
let mut reconnect_task = self.reconnect_task.lock();
*reconnect_task = Some(tokio::spawn({
let inner = self.clone();
async move {
inner.handle_disconnected().await;
inner.reconnect_task.lock().take();
}
}));
}
/// Called every time the PeerConnection or the SignalClient is closed /// Called every time the PeerConnection or the SignalClient is closed
/// We first try to resume the connection, if it fails, we start a full reconnect. /// We first try to resume the connection, if it fails, we start a full reconnect.
async fn handle_disconnected(self: &Arc<Self>) { async fn handle_disconnected(self: &Arc<Self>) {
@@ -331,6 +372,7 @@ impl EngineInner {
full_reconnect = true; full_reconnect = true;
} }
} else { } else {
info!("Connected but failed?");
let _ = self.engine_emitter.send(EngineEvent::Resumed).await; let _ = self.engine_emitter.send(EngineEvent::Resumed).await;
connected = true; connected = true;
break; break;
@@ -344,7 +386,6 @@ impl EngineInner {
if !connected { if !connected {
error!("failed to reconnect after {} attemps", RECONNECT_ATTEMPTS); error!("failed to reconnect after {} attemps", RECONNECT_ATTEMPTS);
let _ = self.engine_emitter.send(EngineEvent::Disconnected).await;
self.close().await; self.close().await;
} }
} }
@@ -353,7 +394,7 @@ impl EngineInner {
/// It recreates a new RTCSession /// It recreates a new RTCSession
async fn try_restart_connection(self: &Arc<Self>) -> EngineResult<()> { async fn try_restart_connection(self: &Arc<Self>) -> EngineResult<()> {
let info = self.session_info.lock().clone().unwrap(); let info = self.session_info.lock().clone().unwrap();
self.close().await; self.terminate_session().await;
self.connect(&info.url, &info.token, info.options).await?; self.connect(&info.url, &info.token, info.options).await?;
self.running_handle self.running_handle
.read() .read()
@@ -12,7 +12,7 @@ use tokio::time::sleep;
use prost::Message; use prost::Message;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{debug, error, trace, warn}; use tracing::{debug, error, info, trace, warn};
use crate::{proto, signal_client}; use crate::{proto, signal_client};
use livekit_webrtc::data_channel::{DataChannel, DataChannelInit, DataState}; use livekit_webrtc::data_channel::{DataChannel, DataChannelInit, DataState};
@@ -24,15 +24,15 @@ use livekit_webrtc::peer_connection_factory::RTCConfiguration;
use crate::proto::data_packet::Value; use crate::proto::data_packet::Value;
use crate::proto::{ use crate::proto::{
data_packet, signal_request, signal_response, DataPacket, JoinResponse, SignalTarget, data_packet, signal_request, signal_response, CandidateProtocol, DataPacket, DisconnectReason,
TrickleRequest, JoinResponse, SignalTarget, TrickleRequest,
}; };
use crate::rtc_engine::lk_runtime::LKRuntime; use crate::rtc_engine::lk_runtime::LKRuntime;
use crate::rtc_engine::pc_transport::PCTransport; use crate::rtc_engine::pc_transport::PCTransport;
use crate::rtc_engine::rtc_events::{RTCEvent, RTCEvents}; use crate::rtc_engine::rtc_events::{RTCEvent, RTCEvents};
use crate::signal_client::{SignalClient, SignalEvent, SignalEvents, SignalOptions}; use crate::signal_client::{SignalClient, SignalEvent, SignalEvents, SignalOptions};
use super::{rtc_events, EngineError, EngineResult}; use super::{rtc_events, EngineError, EngineResult, SimulateScenario};
pub const MAX_ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15); pub const MAX_ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
pub const LOSSY_DC_LABEL: &str = "_lossy"; pub const LOSSY_DC_LABEL: &str = "_lossy";
@@ -52,7 +52,9 @@ pub enum SessionEvent {
receiver: RtpReceiver, receiver: RtpReceiver,
}, },
Close { Close {
reason: String, source: String,
reason: DisconnectReason,
can_reconnect: bool,
}, },
Connected, Connected,
} }
@@ -238,9 +240,9 @@ impl RTCSession {
#[tracing::instrument] #[tracing::instrument]
pub async fn close(self) { pub async fn close(self) {
// Close the tasks // Close the tasks
self.close_emitter.send(true); let _ = self.close_emitter.send(true);
self.rtc_task.await; let _ = self.rtc_task.await;
self.signal_task.await; let _ = self.signal_task.await;
self.inner.close().await; self.inner.close().await;
} }
@@ -259,6 +261,10 @@ impl RTCSession {
pub async fn wait_pc_connection(&self) -> EngineResult<()> { pub async fn wait_pc_connection(&self) -> EngineResult<()> {
self.inner.wait_pc_connection().await self.inner.wait_pc_connection().await
} }
pub async fn simulate_scenario(&self, scenario: SimulateScenario) {
self.inner.simulate_scenario(scenario).await
}
} }
impl RTCSession { impl RTCSession {
@@ -332,7 +338,7 @@ impl SessionInner {
} }
} }
SignalEvent::Close => { SignalEvent::Close => {
self.on_session_disconnected("SignalClient closed"); self.on_session_disconnected("SignalClient closed", DisconnectReason::UnknownReason, true);
} }
} }
} else { } else {
@@ -398,6 +404,9 @@ impl SessionInner {
.await?; .await?;
} }
} }
signal_response::Message::Leave(leave) => {
self.on_session_disconnected("received leave", leave.reason(), leave.can_reconnect);
}
_ => {} _ => {}
} }
@@ -437,7 +446,11 @@ impl SessionInner {
self.pc_state self.pc_state
.store(PCState::Disconnected as u8, Ordering::SeqCst); .store(PCState::Disconnected as u8, Ordering::SeqCst);
self.on_session_disconnected("pc_state failed"); self.on_session_disconnected(
"pc_state failed",
DisconnectReason::UnknownReason,
true,
);
} }
} }
RTCEvent::DataChannel { RTCEvent::DataChannel {
@@ -494,9 +507,11 @@ impl SessionInner {
/// Called when the SignalClient or one of the PeerConnection has lost the connection /// Called when the SignalClient or one of the PeerConnection has lost the connection
/// The RTCEngine may try a reconnect. /// The RTCEngine may try a reconnect.
fn on_session_disconnected(&self, reason: &str) { fn on_session_disconnected(&self, source: &str, reason: DisconnectReason, can_reconnect: bool) {
let _ = self.emitter.send(SessionEvent::Close { let _ = self.emitter.send(SessionEvent::Close {
reason: reason.to_owned(), source: source.to_owned(),
reason,
can_reconnect,
}); });
} }
@@ -507,6 +522,65 @@ impl SessionInner {
self.subscriber_pc.lock().await.close(); self.subscriber_pc.lock().await.close();
} }
#[tracing::instrument]
async fn simulate_scenario(&self, scenario: SimulateScenario) {
match scenario {
SimulateScenario::SignalReconnect => {
self.signal_client.close().await;
}
SimulateScenario::Speaker => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(proto::simulate_scenario::Scenario::SpeakerUpdate(3)),
}))
.await;
}
SimulateScenario::NodeFailure => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(proto::simulate_scenario::Scenario::NodeFailure(true)),
}))
.await;
}
SimulateScenario::ServerLeave => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(proto::simulate_scenario::Scenario::ServerLeave(true)),
}))
.await;
}
SimulateScenario::Migration => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(proto::simulate_scenario::Scenario::Migration(true)),
}))
.await;
}
SimulateScenario::ForceTcp => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(
proto::simulate_scenario::Scenario::SwitchCandidateProtocol(
CandidateProtocol::Tcp as i32,
),
),
}))
.await;
}
SimulateScenario::ForceTls => {
self.signal_client
.send(signal_request::Message::Simulate(proto::SimulateScenario {
scenario: Some(
proto::simulate_scenario::Scenario::SwitchCandidateProtocol(
CandidateProtocol::Tls as i32,
),
),
}))
.await;
}
}
}
#[tracing::instrument(skip(data))] #[tracing::instrument(skip(data))]
async fn publish_data( async fn publish_data(
&self, &self,
@@ -4,6 +4,7 @@ use prost::Message as ProstMessage;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tokio_tungstenite::tungstenite::error::ProtocolError;
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
use tokio_tungstenite::tungstenite::protocol::CloseFrame; use tokio_tungstenite::tungstenite::protocol::CloseFrame;
use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::tungstenite::Message;
@@ -60,6 +61,7 @@ impl SignalStream {
.append_pair("access_token", token) .append_pair("access_token", token)
.append_pair("protocol", PROTOCOL_VERSION.to_string().as_str()) .append_pair("protocol", PROTOCOL_VERSION.to_string().as_str())
.append_pair("reconnect", if options.reconnect { "1" } else { "0" }) .append_pair("reconnect", if options.reconnect { "1" } else { "0" })
.append_pair("sid", &options.sid)
.append_pair( .append_pair(
"auto_subscribe", "auto_subscribe",
if options.auto_subscribe { "1" } else { "0" }, if options.auto_subscribe { "1" } else { "0" },
@@ -69,9 +71,8 @@ impl SignalStream {
if options.adaptive_stream { "1" } else { "0" }, if options.adaptive_stream { "1" } else { "0" },
); );
event!(Level::DEBUG, "connecting to websocket: {}", lk_url); event!(Level::INFO, "connecting to SignalClient: {}", lk_url);
let (ws_stream, _) = connect_async(lk_url).await?; let (ws_stream, _) = connect_async(lk_url).await?;
event!(Level::DEBUG, "connected to websocket");
let _ = emitter.send(SignalEvent::Open).await; let _ = emitter.send(SignalEvent::Open).await;
let (ws_writer, ws_reader) = ws_stream.split(); let (ws_writer, ws_reader) = ws_stream.split();
+47 -8
View File
@@ -10,7 +10,7 @@ use std::sync::{
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use livekit::room::{ConnectionState, Room, RoomError}; use livekit::room::{ConnectionState, Room, RoomError, SimulateScenario};
// Useful default constants for developing // Useful default constants for developing
const DEFAULT_URL: &str = "ws://localhost:7880"; const DEFAULT_URL: &str = "ws://localhost:7880";
@@ -114,6 +114,11 @@ pub fn run(rt: tokio::runtime::Runtime) {
state.connecting.store(false, Ordering::SeqCst); state.connecting.store(false, Ordering::SeqCst);
} }
AsyncCmd::SimulateScenario { scenario } => {
if let Some(handle) = state.room.lock().get_handle() {
let _ = handle.simulate_scenario(scenario).await;
}
}
} }
} }
}); });
@@ -210,7 +215,43 @@ impl App {
if ui.button("WebRTC Stats").clicked() {} if ui.button("WebRTC Stats").clicked() {}
if ui.button("Events").clicked() {} if ui.button("Events").clicked() {}
}); });
ui.menu_button("Simulate", |ui| {}); ui.menu_button("Simulate", |ui| {
if ui.button("SignalReconnect").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::SignalReconnect,
});
}
if ui.button("Speaker").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::Speaker,
});
}
if ui.button("NodeFailure").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::NodeFailure,
});
}
if ui.button("ServerLeave").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::ServerLeave,
});
}
if ui.button("Migration").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::Migration,
});
}
if ui.button("ForceTcp").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::ForceTcp,
});
}
if ui.button("ForceTls").clicked() {
let _ = self.cmd_tx.send(AsyncCmd::SimulateScenario {
scenario: SimulateScenario::ForceTls,
});
}
});
}); });
}); });
@@ -236,12 +277,10 @@ impl App {
if ui.button("Connect").clicked() { if ui.button("Connect").clicked() {
self.connection_failure = None; self.connection_failure = None;
self.cmd_tx let _ = self.cmd_tx.send(AsyncCmd::RoomConnect {
.send(AsyncCmd::RoomConnect { url: self.lk_url.clone(),
url: self.lk_url.clone(), token: self.lk_token.clone(),
token: self.lk_token.clone(), });
})
.unwrap();
} }
if connecting { if connecting {
+2 -1
View File
@@ -1,8 +1,9 @@
use livekit::events::TrackSubscribedEvent; use livekit::{events::TrackSubscribedEvent, room::SimulateScenario};
#[derive(Debug)] #[derive(Debug)]
pub enum AsyncCmd { pub enum AsyncCmd {
RoomConnect { url: String, token: String }, RoomConnect { url: String, token: String },
SimulateScenario { scenario: SimulateScenario }
} }
#[derive(Debug)] #[derive(Debug)]