Merge pull request #5868 from dignow/fix/reconnect_potential_deadlock

fix, reconnect deadlock, introduce connection round control
This commit is contained in:
RustDesk 2023-09-30 22:26:57 +08:00 committed by GitHub
commit 44554cb54b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 12 deletions

View File

@ -106,7 +106,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
} }
pub async fn io_loop(&mut self, key: &str, token: &str) { pub async fn io_loop(&mut self, key: &str, token: &str, round: u32) {
let mut last_recv_time = Instant::now(); let mut last_recv_time = Instant::now();
let mut received = false; let mut received = false;
let conn_type = if self.handler.is_file_transfer() { let conn_type = if self.handler.is_file_transfer() {
@ -125,6 +125,11 @@ impl<T: InvokeUiSession> Remote<T> {
.await .await
{ {
Ok((mut peer, direct, pk)) => { Ok((mut peer, direct, pk)) => {
self.handler
.connection_round_state
.lock()
.unwrap()
.set_connected();
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
self.handler.update_direct(Some(direct)); self.handler.update_direct(Some(direct));
if conn_type == ConnType::DEFAULT_CONN { if conn_type == ConnType::DEFAULT_CONN {
@ -245,11 +250,21 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler.on_establish_connection_error(err.to_string()); self.handler.on_establish_connection_error(err.to_string());
} }
} }
// set_disconnected_ok is used to check if new connection round is started.
let _set_disconnected_ok = self
.handler
.connection_round_state
.lock()
.unwrap()
.set_disconnected(round);
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_stop_clipboard(&self.handler.session_id); if _set_disconnected_ok {
Client::try_stop_clipboard(&self.handler.session_id);
}
#[cfg(windows)] #[cfg(windows)]
{ if _set_disconnected_ok {
let conn_id = self.client_conn_id; let conn_id = self.client_conn_id;
ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 { ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 {
empty_clipboard(context, conn_id); empty_clipboard(context, conn_id);

View File

@ -805,7 +805,8 @@ pub fn session_start_(
if !is_pre_added { if !is_pre_added {
let session = session.clone(); let session = session.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
io_loop(session); let round = session.connection_round_state.lock().unwrap().new_round();
io_loop(session, round);
}); });
} }
Ok(()) Ok(())

View File

@ -8,8 +8,10 @@ use std::{
use flutter_rust_bridge::StreamSink; use flutter_rust_bridge::StreamSink;
use crate::{ use crate::{
define_method_prefix, flutter::FlutterHandler, flutter_ffi::EventToUI, define_method_prefix,
plugin::MSG_TO_UI_TYPE_PLUGIN_EVENT, ui_session_interface::Session, flutter::FlutterHandler,
flutter_ffi::EventToUI,
ui_session_interface::{ConnectionState, Session},
}; };
const MSG_TO_UI_TYPE_SESSION_CREATED: &str = "session_created"; const MSG_TO_UI_TYPE_SESSION_CREATED: &str = "session_created";
@ -61,7 +63,8 @@ impl PluginNativeHandler for PluginNativeSessionHandler {
let sessions = SESSION_HANDLER.sessions.read().unwrap(); let sessions = SESSION_HANDLER.sessions.read().unwrap();
for session in sessions.iter() { for session in sessions.iter() {
if session.id == id { if session.id == id {
crate::ui_session_interface::io_loop(session.clone()); let round = session.connection_round_state.lock().unwrap().new_round();
crate::ui_session_interface::io_loop(session.clone(), round);
} }
} }
} }

View File

@ -62,6 +62,7 @@ pub struct Session<T: InvokeUiSession> {
pub server_file_transfer_enabled: Arc<RwLock<bool>>, pub server_file_transfer_enabled: Arc<RwLock<bool>>,
pub server_clipboard_enabled: Arc<RwLock<bool>>, pub server_clipboard_enabled: Arc<RwLock<bool>>,
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>, pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -79,6 +80,56 @@ pub struct ChangeDisplayRecord {
height: i32, height: i32,
} }
enum ConnectionState {
Connecting,
Connected,
Disconnected,
}
/// ConnectionRoundState is used to control the reconnecting logic.
pub struct ConnectionRoundState {
round: u32,
state: ConnectionState,
}
impl ConnectionRoundState {
pub fn new_round(&mut self) -> u32 {
self.round += 1;
self.state = ConnectionState::Connecting;
self.round
}
pub fn set_connected(&mut self) {
self.state = ConnectionState::Connected;
}
pub fn is_round_gt(&self, round: u32) -> bool {
if round == u32::MAX && self.round == 0 {
true
} else {
round < self.round
}
}
pub fn set_disconnected(&mut self, round: u32) -> bool {
if self.is_round_gt(round) {
false
} else {
self.state = ConnectionState::Disconnected;
true
}
}
}
impl Default for ConnectionRoundState {
fn default() -> Self {
Self {
round: 0,
state: ConnectionState::Connecting,
}
}
}
impl Default for ChangeDisplayRecord { impl Default for ChangeDisplayRecord {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -833,16 +884,28 @@ impl<T: InvokeUiSession> Session<T> {
} }
pub fn reconnect(&self, force_relay: bool) { pub fn reconnect(&self, force_relay: bool) {
self.send(Data::Close); // 1. If current session is connecting, do not reconnect.
// 2. If the connection is established, send `Data::Close`.
// 3. If the connection is disconnected, do nothing.
let mut connection_round_state_lock = self.connection_round_state.lock().unwrap();
match connection_round_state_lock.state {
ConnectionState::Connecting => return,
ConnectionState::Connected => self.send(Data::Close),
ConnectionState::Disconnected => {}
}
let round = connection_round_state_lock.new_round();
drop(connection_round_state_lock);
let cloned = self.clone(); let cloned = self.clone();
// override only if true // override only if true
if true == force_relay { if true == force_relay {
cloned.lc.write().unwrap().force_relay = true; cloned.lc.write().unwrap().force_relay = true;
} }
let mut lock = self.thread.lock().unwrap(); let mut lock = self.thread.lock().unwrap();
lock.take().map(|t| t.join()); // No need to join the previous thread, because it will exit automatically.
// And the previous thread will not change important states.
*lock = Some(std::thread::spawn(move || { *lock = Some(std::thread::spawn(move || {
io_loop(cloned); io_loop(cloned, round);
})); }));
} }
@ -1283,7 +1346,7 @@ impl<T: InvokeUiSession> Session<T> {
} }
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) { pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
// It is ok to call this function multiple times. // It is ok to call this function multiple times.
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if !handler.is_file_transfer() && !handler.is_port_forward() { if !handler.is_file_transfer() && !handler.is_port_forward() {
@ -1402,7 +1465,7 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
frame_count, frame_count,
decode_fps, decode_fps,
); );
remote.io_loop(&key, &token).await; remote.io_loop(&key, &token, round).await;
remote.sync_jobs_status_to_local().await; remote.sync_jobs_status_to_local().await;
} }