Add reconnection logic to remote server (#9289)

Migrated from /Users/kevinyang/Documents/GitHub/warp-internal via
`script/migrate-private-to-public`.

## Commits

- 823458b Add reconnect logic to remote server
- 104aafb tech spec
- c4039c3 clippy
- c4e3a39 nit
This commit is contained in:
Yunfan Yang
2026-04-28 18:02:55 -04:00
committed by GitHub
parent 00df35b5dc
commit 2d0d88fea6
8 changed files with 743 additions and 269 deletions
+43 -13
View File
@@ -36,6 +36,9 @@ use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent};
use crate::terminal::event::ExecutedExecutorCommandEvent;
use crate::terminal::ShellHost;
use crate::terminal::ShellLaunchData;
#[cfg(feature = "local_tty")]
use command_executor::remote_server_executor::RemoteServerCommandExecutor;
use parking_lot::{Mutex, RwLock};
use crate::terminal::shell::{Shell, ShellType};
use crate::terminal::warpify::SubshellSource;
@@ -172,6 +175,18 @@ impl Sessions {
| RemoteServerManagerEvent::BinaryInstallComplete { .. }
| RemoteServerManagerEvent::ClientRequestFailed { .. }
| RemoteServerManagerEvent::ServerMessageDecodingError { .. } => {}
RemoteServerManagerEvent::SessionReconnected {
session_id: sid,
client,
..
} => {
if let Some(session) = sessions.sessions.get(sid) {
let new_executor =
Arc::new(RemoteServerCommandExecutor::new(*sid, client.clone()));
session.set_command_executor(new_executor);
log::info!("Swapped command executor for session {sid:?} after reconnect");
}
}
});
}
#[cfg(not(feature = "local_tty"))]
@@ -847,14 +862,16 @@ impl From<BootstrapSessionType> for SessionType {
pub struct Session {
info: SessionInfo,
external_commands: Arc<OnceCell<HashSet<SmolStr>>>,
command_executor: Arc<dyn CommandExecutor>,
/// The command executor for this session. Behind a `RwLock` so it can be
/// swapped after a remote server reconnect (via `set_command_executor`).
command_executor: RwLock<Arc<dyn CommandExecutor>>,
load_external_commands_future: OnceCell<Shared<BoxFuture<'static, ()>>>,
command_case_sensitivity: TopLevelCommandCaseSensitivity,
/// The authoritative session type, initially derived from the
/// [`BootstrapSessionType`] in `SessionInfo` and updated by [`Sessions`]
/// when `RemoteServerManager` reports a connected session (to fill in the
/// `host_id`). Interior mutability allows updating through `Arc<Session>`.
session_type: parking_lot::Mutex<SessionType>,
session_type: Mutex<SessionType>,
}
impl Session {
@@ -873,10 +890,10 @@ impl Session {
Self {
info: session_info,
external_commands: Arc::new(OnceCell::new()),
command_executor,
command_executor: RwLock::new(command_executor),
load_external_commands_future: Default::default(),
command_case_sensitivity,
session_type: parking_lot::Mutex::new(session_type),
session_type: Mutex::new(session_type),
}
}
@@ -1025,9 +1042,17 @@ impl Session {
&self.info.subshell_info
}
/// Replaces the command executor for this session. Used after a remote
/// server reconnect to swap in a new `RemoteServerCommandExecutor`
/// backed by the reconnected client.
pub fn set_command_executor(&self, executor: Arc<dyn CommandExecutor>) {
*self.command_executor.write() = executor;
}
/// Returns true if the session is employing in-band command execution to run generators.
pub fn is_using_in_band_command_execution(&self) -> bool {
self.command_executor
.read()
.as_ref()
.as_any()
.downcast_ref::<InBandCommandExecutor>()
@@ -1089,8 +1114,8 @@ impl Session {
.path
.as_deref()
.map(|path| HashMap::from_iter([("PATH".to_string(), path.to_string())]));
let windows_results = self
.command_executor
let executor = self.command_executor.read().clone();
let windows_results = executor
.execute_command(
ShellType::PowerShell.shell_command_to_get_executables(),
&Shell::new(ShellType::PowerShell, None, None, Default::default(), None),
@@ -1381,7 +1406,10 @@ impl Session {
environment_variables: Option<HashMap<String, String>>,
execute_command_options: ExecuteCommandOptions,
) -> Result<CommandOutput> {
self.command_executor
// Clone the Arc out of the lock so we don't hold the read guard
// across the await point.
let executor = self.command_executor.read().clone();
executor
.execute_command(
command,
&self.info.shell,
@@ -1394,11 +1422,13 @@ impl Session {
/// Whether the backing executor for the session supports execution of commands in parallel.
pub fn supports_parallel_command_execution(&self) -> bool {
self.command_executor.supports_parallel_command_execution()
self.command_executor
.read()
.supports_parallel_command_execution()
}
pub fn cancel_active_commands(&self) {
self.command_executor.cancel_active_commands();
self.command_executor.read().cancel_active_commands();
}
pub async fn git_branches_for_command_corrections(&self, working_dir: &str) -> Vec<String> {
@@ -1644,10 +1674,10 @@ pub mod testing {
Self {
info,
external_commands: Default::default(),
command_executor: Arc::new(TestCommandExecutor::default()),
command_executor: RwLock::new(Arc::new(TestCommandExecutor::default())),
load_external_commands_future: Default::default(),
command_case_sensitivity: TopLevelCommandCaseSensitivity::CaseSensitive,
session_type: parking_lot::Mutex::new(session_type),
session_type: Mutex::new(session_type),
}
}
@@ -1659,10 +1689,10 @@ pub mod testing {
Self {
info,
external_commands: Default::default(),
command_executor: Arc::new(TestCommandExecutor::default()),
command_executor: RwLock::new(Arc::new(TestCommandExecutor::default())),
load_external_commands_future: Default::default(),
command_case_sensitivity: TopLevelCommandCaseSensitivity::CaseSensitive,
session_type: parking_lot::Mutex::new(session_type),
session_type: Mutex::new(session_type),
}
}
+1
View File
@@ -4376,6 +4376,7 @@ impl TerminalView {
}
}
RemoteServerManagerEvent::SessionConnecting { .. }
| RemoteServerManagerEvent::SessionReconnected { .. }
| RemoteServerManagerEvent::HostConnected { .. }
| RemoteServerManagerEvent::HostDisconnected { .. }
| RemoteServerManagerEvent::RepoMetadataSnapshot { .. }