use callbacks on internal events (#100)
This commit is contained in:
@@ -19,7 +19,7 @@ livekit = { path = "../livekit", version = "0.1.1" }
|
||||
livekit-protocol = { path = "../livekit-protocol", version = "0.1.0" }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink"] }
|
||||
parking_lot = { version = "0.12.1", features=["send_guard"] }
|
||||
parking_lot = { version = "0.12.1", features=["deadlock_detection"] }
|
||||
prost = "0.11.0"
|
||||
prost-types = "0.11.1"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -62,7 +62,7 @@ pub extern "C" fn livekit_ffi_request(
|
||||
|
||||
let handle_id = server::FFI_SERVER.next_id();
|
||||
server::FFI_SERVER
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(handle_id, Box::new(res));
|
||||
|
||||
handle_id
|
||||
@@ -71,8 +71,5 @@ pub extern "C" fn livekit_ffi_request(
|
||||
#[no_mangle]
|
||||
pub extern "C" fn livekit_ffi_drop_handle(handle_id: FfiHandleId) -> bool {
|
||||
// Free the memory
|
||||
server::FFI_SERVER
|
||||
.ffi_handles()
|
||||
.remove(&handle_id)
|
||||
.is_some()
|
||||
server::FFI_SERVER.ffi_handles.remove(&handle_id).is_some()
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl FfiAudioSream {
|
||||
.id as FfiHandleId;
|
||||
|
||||
let track = server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.get(&handle_id)
|
||||
.ok_or(FfiError::InvalidRequest("track not found"))?;
|
||||
|
||||
@@ -77,7 +77,7 @@ impl FfiAudioSream {
|
||||
// Store the new audio stream and return the info
|
||||
let info = proto::AudioStreamInfo::from(&audio_stream);
|
||||
server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(audio_stream.handle_id, Box::new(audio_stream));
|
||||
|
||||
Ok(info)
|
||||
@@ -110,7 +110,7 @@ impl FfiAudioSream {
|
||||
let handle_id = server.next_id();
|
||||
let buffer_info = proto::AudioFrameBufferInfo::from(handle_id, &frame);
|
||||
|
||||
server.ffi_handles().insert(handle_id, Box::new(frame));
|
||||
server.ffi_handles.insert(handle_id, Box::new(frame));
|
||||
|
||||
if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent(
|
||||
proto::AudioStreamEvent {
|
||||
@@ -164,7 +164,7 @@ impl FfiAudioSource {
|
||||
let source_info = proto::AudioSourceInfo::from(&audio_source);
|
||||
|
||||
server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(audio_source.handle_id, Box::new(audio_source));
|
||||
|
||||
Ok(source_info)
|
||||
@@ -184,7 +184,7 @@ impl FfiAudioSource {
|
||||
.id as FfiHandleId;
|
||||
|
||||
let frame = server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.get(&buffer_handle)
|
||||
.ok_or(FfiError::InvalidRequest("handle not found"))?;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use livekit::webrtc::prelude::*;
|
||||
use livekit::webrtc::video_frame::{native::I420BufferExt, BoxVideoFrameBuffer, I420Buffer};
|
||||
use parking_lot::Mutex;
|
||||
use prost::Message;
|
||||
use std::collections::HashMap;
|
||||
use std::slice;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
@@ -29,12 +28,12 @@ pub struct FfiConfig {
|
||||
}
|
||||
|
||||
pub struct FfiServer {
|
||||
rooms: Mutex<HashMap<RoomSid, FfiHandleId>>,
|
||||
/// Store all Ffi handles inside an HashMap, if this isn't efficient enough
|
||||
/// We can still use Box::into_raw & Box::from_raw in the future (but keep it safe for now)
|
||||
ffi_handles: DashMap<FfiHandleId, FfiHandle>,
|
||||
pub ffi_handles: DashMap<FfiHandleId, FfiHandle>,
|
||||
pub async_runtime: tokio::runtime::Runtime,
|
||||
|
||||
next_id: AtomicUsize,
|
||||
async_runtime: tokio::runtime::Runtime,
|
||||
config: Mutex<Option<FfiConfig>>,
|
||||
}
|
||||
|
||||
@@ -42,8 +41,30 @@ impl Default for FfiServer {
|
||||
fn default() -> Self {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
// Create a background thread which checks for deadlocks every 10s
|
||||
{
|
||||
use parking_lot::deadlock;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::error!("{} deadlocks detected", deadlocks.len());
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
log::error!("Deadlock #{}", i);
|
||||
for t in threads {
|
||||
log::error!("Thread Id {:#?}: \n{:#?}", t.thread_id(), t.backtrace());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
rooms: Default::default(),
|
||||
ffi_handles: Default::default(),
|
||||
next_id: AtomicUsize::new(1), // 0 is invalid
|
||||
async_runtime: tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -60,13 +81,19 @@ impl Default for FfiServer {
|
||||
impl FfiServer {
|
||||
pub async fn dispose(&'static self) {
|
||||
// Close all rooms
|
||||
for (_, room_handle) in self.rooms.lock().drain() {
|
||||
let room = self.ffi_handles.remove(&room_handle);
|
||||
if let Some(room) = room {
|
||||
let ffi_room = room.1.downcast::<room::FfiRoom>().unwrap();
|
||||
ffi_room.close().await;
|
||||
log::info!("disposing the FfiServer, closing all rooms...");
|
||||
|
||||
let mut rooms = Vec::new();
|
||||
for handle in self.ffi_handles.iter_mut() {
|
||||
if let Some(handle) = handle.value().downcast_ref::<room::HandleType>() {
|
||||
rooms.push(handle.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for room in rooms {
|
||||
room.close().await;
|
||||
}
|
||||
|
||||
// Drop all handles
|
||||
self.ffi_handles.clear();
|
||||
|
||||
@@ -78,14 +105,6 @@ impl FfiServer {
|
||||
self.next_id.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn ffi_handles(&'static self) -> &DashMap<FfiHandleId, FfiHandle> {
|
||||
&self.ffi_handles
|
||||
}
|
||||
|
||||
pub fn rooms(&'static self) -> &Mutex<HashMap<RoomSid, FfiHandleId>> {
|
||||
&self.rooms
|
||||
}
|
||||
|
||||
pub fn send_event(&'static self, message: proto::ffi_event::Message) -> FfiResult<()> {
|
||||
let callback_fn = self
|
||||
.config
|
||||
@@ -128,14 +147,13 @@ impl FfiServer {
|
||||
) -> FfiResult<proto::DisposeResponse> {
|
||||
*self.config.lock() = None;
|
||||
|
||||
let close = self.dispose();
|
||||
if !dispose.r#async {
|
||||
self.async_runtime.block_on(close);
|
||||
self.async_runtime.block_on(self.dispose());
|
||||
Ok(proto::DisposeResponse::default())
|
||||
} else {
|
||||
let async_id = self.next_id();
|
||||
self.async_runtime.spawn(async move {
|
||||
close.await;
|
||||
self.dispose().await;
|
||||
});
|
||||
Ok(proto::DisposeResponse {
|
||||
async_id: Some(proto::FfiAsyncId {
|
||||
@@ -197,17 +215,19 @@ impl FfiServer {
|
||||
.ok_or(FfiError::InvalidRequest("room_handle is empty"))?
|
||||
.id as FfiHandleId;
|
||||
|
||||
let ffi_room = self
|
||||
.ffi_handles
|
||||
.remove(&room_handle)
|
||||
.ok_or(FfiError::InvalidRequest("room not found"))?
|
||||
.1;
|
||||
|
||||
let ffi_room = ffi_room
|
||||
.downcast::<room::FfiRoom>()
|
||||
.map_err(|_| FfiError::InvalidRequest("room is not a FfiRoom"))?;
|
||||
|
||||
self.async_runtime.spawn(async move {
|
||||
let mut ffi_room = self
|
||||
.ffi_handles
|
||||
.get_mut(&room_handle)
|
||||
.ok_or(FfiError::InvalidRequest("room not found"))
|
||||
.unwrap();
|
||||
|
||||
let ffi_room = ffi_room
|
||||
.value_mut()
|
||||
.downcast_mut::<room::HandleType>()
|
||||
.ok_or(FfiError::InvalidRequest("room is not a FfiRoom"))
|
||||
.unwrap();
|
||||
|
||||
ffi_room.close().await;
|
||||
let _ = self.send_event(proto::ffi_event::Message::Disconnect(
|
||||
proto::DisconnectCallback {
|
||||
@@ -313,7 +333,7 @@ impl FfiServer {
|
||||
.ok_or(FfiError::InvalidRequest("room not found"))?;
|
||||
|
||||
let ffi_room = ffi_room
|
||||
.downcast_ref::<room::FfiRoom>()
|
||||
.downcast_ref::<room::HandleType>()
|
||||
.ok_or(FfiError::InvalidRequest("room is not a FfiRoom"))?;
|
||||
|
||||
// Push the data to an async queue (avoid blocking and keep the order)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use crate::server::FfiServer;
|
||||
use crate::{proto, FfiAsyncId, FfiError, FfiHandleId, FfiResult};
|
||||
use livekit::prelude::*;
|
||||
use parking_lot::Mutex;
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub type HandleType = Arc<FfiRoom>;
|
||||
|
||||
struct DataPacket {
|
||||
data: Vec<u8>,
|
||||
kind: DataPacketKind,
|
||||
@@ -13,11 +16,15 @@ struct DataPacket {
|
||||
async_id: FfiAsyncId,
|
||||
}
|
||||
|
||||
pub struct FfiRoom {
|
||||
room: Arc<Room>,
|
||||
struct Handle {
|
||||
event_handle: JoinHandle<()>,
|
||||
data_handle: JoinHandle<()>,
|
||||
close_tx: broadcast::Sender<()>,
|
||||
}
|
||||
|
||||
pub struct FfiRoom {
|
||||
room: Arc<Room>,
|
||||
handle: Mutex<Option<Handle>>,
|
||||
data_tx: mpsc::UnboundedSender<DataPacket>,
|
||||
}
|
||||
|
||||
@@ -49,16 +56,17 @@ impl FfiRoom {
|
||||
.async_runtime
|
||||
.spawn(data_task(server, room.clone(), data_rx, close_rx));
|
||||
|
||||
let ffi_room = Self {
|
||||
let ffi_room = Arc::new(Self {
|
||||
room: room.clone(),
|
||||
event_handle,
|
||||
data_handle,
|
||||
close_tx,
|
||||
handle: Mutex::new(Some(Handle {
|
||||
event_handle,
|
||||
data_handle,
|
||||
close_tx,
|
||||
})),
|
||||
data_tx,
|
||||
};
|
||||
});
|
||||
|
||||
server.ffi_handles().insert(next_id, Box::new(ffi_room));
|
||||
server.rooms().lock().insert(room.sid(), next_id);
|
||||
server.ffi_handles.insert(next_id, Box::new(ffi_room));
|
||||
|
||||
let room_info = proto::RoomInfo::from_room(next_id, &room);
|
||||
Ok(room_info)
|
||||
@@ -92,11 +100,15 @@ impl FfiRoom {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn close(self) {
|
||||
pub async fn close(&self) {
|
||||
let _ = self.room.close().await;
|
||||
let _ = self.close_tx.send(());
|
||||
let _ = self.event_handle.await;
|
||||
let _ = self.data_handle.await;
|
||||
|
||||
let handle = self.handle.lock().take();
|
||||
if let Some(handle) = handle {
|
||||
let _ = handle.close_tx.send(());
|
||||
let _ = handle.event_handle.await;
|
||||
let _ = handle.data_handle.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room(&self) -> &Arc<Room> {
|
||||
@@ -135,23 +147,16 @@ async fn data_task(
|
||||
|
||||
async fn room_task(
|
||||
server: &'static FfiServer,
|
||||
room: Arc<Room>,
|
||||
_room: Arc<Room>,
|
||||
room_handle: FfiHandleId,
|
||||
mut events: mpsc::UnboundedReceiver<livekit::RoomEvent>,
|
||||
mut close_rx: broadcast::Receiver<()>,
|
||||
) {
|
||||
server
|
||||
.async_runtime
|
||||
.spawn(participant_task(Participant::Local(
|
||||
room.local_participant(),
|
||||
)));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = events.recv() => {
|
||||
let message = match event {
|
||||
if let Some(message)= match event {
|
||||
RoomEvent::ParticipantConnected(participant) => {
|
||||
server.async_runtime.spawn(participant_task(Participant::Remote(participant.clone())));
|
||||
Some(proto::room_event::Message::ParticipantConnected(
|
||||
proto::ParticipantConnected {
|
||||
info: Some(proto::ParticipantInfo::from(&participant)),
|
||||
@@ -190,7 +195,7 @@ async fn room_task(
|
||||
} => {
|
||||
let handle_id = server.next_id() as FfiHandleId;
|
||||
let track_info = proto::TrackInfo::from_remote_track(handle_id, &track);
|
||||
server.ffi_handles().insert(handle_id, Box::new(Track::from(track)));
|
||||
server.ffi_handles.insert(handle_id, Box::new(Track::from(track)));
|
||||
|
||||
Some(proto::room_event::Message::TrackSubscribed(
|
||||
proto::TrackSubscribed {
|
||||
@@ -210,12 +215,11 @@ async fn room_task(
|
||||
},
|
||||
)),
|
||||
_ => None
|
||||
};
|
||||
|
||||
if message.is_some() {
|
||||
} {
|
||||
// Send the event to the FfiClient
|
||||
let _ = server.send_event(proto::ffi_event::Message::RoomEvent(proto::RoomEvent{
|
||||
room_handle: Some(room_handle.into()),
|
||||
message
|
||||
message: Some(message)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -226,10 +230,3 @@ async fn room_task(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn participant_task(participant: Participant) {
|
||||
let mut participant_events = participant.register_observer();
|
||||
while let Some(_event) = participant_events.recv().await {
|
||||
// TODO(theomonnom): convert event to proto
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ impl TestScope {
|
||||
impl Drop for TestScope {
|
||||
fn drop(&mut self) {
|
||||
// At the end of a test, no more handle should exist
|
||||
assert!(server::FFI_SERVER.ffi_handles().is_empty());
|
||||
assert!(server::FFI_SERVER.ffi_handles.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl FfiVideoStream {
|
||||
.id as FfiHandleId;
|
||||
|
||||
let track = server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.get(&handle_id)
|
||||
.ok_or(FfiError::InvalidRequest("track not found"))?;
|
||||
|
||||
@@ -75,7 +75,7 @@ impl FfiVideoStream {
|
||||
// Store the new video stream and return the info
|
||||
let info = proto::VideoStreamInfo::from(&stream);
|
||||
server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(stream.handle_id, Box::new(stream));
|
||||
|
||||
Ok(info)
|
||||
@@ -110,7 +110,7 @@ impl FfiVideoStream {
|
||||
let buffer_info = proto::VideoFrameBufferInfo::from(handle_id, &frame.buffer);
|
||||
|
||||
server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(handle_id, Box::new(frame.buffer));
|
||||
|
||||
if let Err(err) = server.send_event(proto::ffi_event::Message::VideoStreamEvent(
|
||||
@@ -167,7 +167,7 @@ impl FfiVideoSource {
|
||||
let source_info = proto::VideoSourceInfo::from(&video_source);
|
||||
|
||||
server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.insert(video_source.handle_id, Box::new(video_source));
|
||||
|
||||
Ok(source_info)
|
||||
@@ -191,7 +191,7 @@ impl FfiVideoSource {
|
||||
.id as FfiHandleId;
|
||||
|
||||
let buffer = server
|
||||
.ffi_handles()
|
||||
.ffi_handles
|
||||
.get(&buffer_handle)
|
||||
.ok_or(FfiError::InvalidRequest("handle not found"))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user