feat(v2): refactor reconnection probe to TCP-only and add frontend listeners
Replace attempt_ws_connect() with attempt_tcp_probe() in RemoteManager to avoid allocating per-connection resources (PtyManager, SidecarManager) on the relay during reconnection probes. Add onRemoteMachineReconnecting and onRemoteMachineReconnectReady event listeners in remote-bridge.ts. Wire machines store to auto-reconnect when relay becomes reachable.
This commit is contained in:
parent
218570ac35
commit
71100da125
3 changed files with 59 additions and 23 deletions
|
|
@ -269,14 +269,14 @@ impl RemoteManager {
|
||||||
"backoffSecs": delay.as_secs(),
|
"backoffSecs": delay.as_secs(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Try to get config for reconnection
|
// Try to get URL for TCP probe
|
||||||
let config = {
|
let url = {
|
||||||
let machines = reconnect_machines.lock().await;
|
let machines = reconnect_machines.lock().await;
|
||||||
machines.get(&reconnect_mid).map(|m| (m.config.url.clone(), m.config.token.clone()))
|
machines.get(&reconnect_mid).map(|m| m.config.url.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((url, token)) = config {
|
if let Some(url) = url {
|
||||||
if attempt_ws_connect(&url, &token).await.is_ok() {
|
if attempt_tcp_probe(&url).await.is_ok() {
|
||||||
log::info!("Reconnection probe succeeded for {reconnect_mid}");
|
log::info!("Reconnection probe succeeded for {reconnect_mid}");
|
||||||
// Mark as ready for reconnection — frontend should call connect()
|
// Mark as ready for reconnection — frontend should call connect()
|
||||||
let _ = reconnect_app.emit("remote-machine-reconnect-ready", &serde_json::json!({
|
let _ = reconnect_app.emit("remote-machine-reconnect-ready", &serde_json::json!({
|
||||||
|
|
@ -412,30 +412,25 @@ impl RemoteManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Probe whether a relay is reachable (connect + immediate close).
|
/// Probe whether a relay is reachable via TCP connect only (no WS upgrade).
|
||||||
async fn attempt_ws_connect(url: &str, token: &str) -> Result<(), String> {
|
/// This avoids allocating per-connection resources (PtyManager, SidecarManager) on the relay.
|
||||||
let request = tokio_tungstenite::tungstenite::http::Request::builder()
|
async fn attempt_tcp_probe(url: &str) -> Result<(), String> {
|
||||||
.uri(url)
|
let host = extract_host(url).ok_or_else(|| "Invalid URL".to_string())?;
|
||||||
.header("Authorization", format!("Bearer {token}"))
|
// Parse host:port, default to 9750 if no port
|
||||||
.header("Sec-WebSocket-Key", tokio_tungstenite::tungstenite::handshake::client::generate_key())
|
let addr = if host.contains(':') {
|
||||||
.header("Sec-WebSocket-Version", "13")
|
host.clone()
|
||||||
.header("Connection", "Upgrade")
|
} else {
|
||||||
.header("Upgrade", "websocket")
|
format!("{host}:9750")
|
||||||
.header("Host", extract_host(url).unwrap_or_default())
|
};
|
||||||
.body(())
|
|
||||||
.map_err(|e| format!("Request build failed: {e}"))?;
|
|
||||||
|
|
||||||
let (ws, _) = tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
std::time::Duration::from_secs(5),
|
std::time::Duration::from_secs(5),
|
||||||
tokio_tungstenite::connect_async(request),
|
tokio::net::TcpStream::connect(&addr),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| "Connection timeout".to_string())?
|
.map_err(|_| "Connection timeout".to_string())?
|
||||||
.map_err(|e| format!("Connection failed: {e}"))?;
|
.map_err(|e| format!("TCP connect failed: {e}"))?;
|
||||||
|
|
||||||
// Close immediately — this was just a probe
|
|
||||||
let (mut tx, _) = ws.split();
|
|
||||||
let _ = tx.close().await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,3 +120,24 @@ export async function onRemoteError(
|
||||||
callback(event.payload);
|
callback(event.payload);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RemoteReconnectingEvent {
|
||||||
|
machineId: string;
|
||||||
|
backoffSecs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onRemoteMachineReconnecting(
|
||||||
|
callback: (msg: RemoteReconnectingEvent) => void,
|
||||||
|
): Promise<UnlistenFn> {
|
||||||
|
return listen<RemoteReconnectingEvent>('remote-machine-reconnecting', (event) => {
|
||||||
|
callback(event.payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onRemoteMachineReconnectReady(
|
||||||
|
callback: (msg: RemoteMachineEvent) => void,
|
||||||
|
): Promise<UnlistenFn> {
|
||||||
|
return listen<RemoteMachineEvent>('remote-machine-reconnect-ready', (event) => {
|
||||||
|
callback(event.payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import {
|
||||||
onRemoteMachineReady,
|
onRemoteMachineReady,
|
||||||
onRemoteMachineDisconnected,
|
onRemoteMachineDisconnected,
|
||||||
onRemoteError,
|
onRemoteError,
|
||||||
|
onRemoteMachineReconnecting,
|
||||||
|
onRemoteMachineReconnectReady,
|
||||||
type RemoteMachineConfig,
|
type RemoteMachineConfig,
|
||||||
type RemoteMachineInfo,
|
type RemoteMachineInfo,
|
||||||
} from '../adapters/remote-bridge';
|
} from '../adapters/remote-bridge';
|
||||||
|
|
@ -94,4 +96,22 @@ export async function initMachineListeners(): Promise<void> {
|
||||||
notify('error', `Error from ${machine.label}: ${msg.error}`);
|
notify('error', `Error from ${machine.label}: ${msg.error}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await onRemoteMachineReconnecting((msg) => {
|
||||||
|
const machine = machines.find(m => m.id === msg.machineId);
|
||||||
|
if (machine) {
|
||||||
|
machine.status = 'reconnecting';
|
||||||
|
notify('info', `Reconnecting to ${machine.label} in ${msg.backoffSecs}s…`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await onRemoteMachineReconnectReady((msg) => {
|
||||||
|
const machine = machines.find(m => m.id === msg.machineId);
|
||||||
|
if (machine) {
|
||||||
|
notify('info', `${machine.label} reachable — reconnecting…`);
|
||||||
|
connectMachine(msg.machineId).catch((e) => {
|
||||||
|
notify('error', `Auto-reconnect failed for ${machine.label}: ${e}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue