use std::time::Duration;
use crate::apis::configuration::Configuration;
use crate::websocket::{
ConsoleConnector, TerminalSession, TerminalTarget, VncSession, VncTarget, WsError,
};
#[derive(Debug, Clone)]
pub struct RetryOptions {
pub max_retries: u32,
pub initial_delay: Duration,
pub max_delay: Duration,
pub backoff_multiplier: f64,
}
impl Default for RetryOptions {
fn default() -> Self {
Self {
max_retries: 10,
initial_delay: Duration::from_millis(500),
max_delay: Duration::from_secs(30),
backoff_multiplier: 2.0,
}
}
}
fn delay_for(attempt: u32, opts: &RetryOptions) -> Duration {
let mut d = opts.initial_delay.as_secs_f64();
for _ in 0..attempt {
d *= opts.backoff_multiplier;
}
let max = opts.max_delay.as_secs_f64();
Duration::from_secs_f64(d.min(max))
}
pub struct ResilientTerminalSession {
cfg: Configuration,
target: TerminalTarget,
opts: RetryOptions,
session: TerminalSession,
closed: bool,
}
impl ResilientTerminalSession {
pub async fn open(
cfg: Configuration,
target: TerminalTarget,
opts: RetryOptions,
) -> Result<Self, WsError> {
let session = ConsoleConnector::default()
.open_terminal(&cfg, target.clone())
.await?;
Ok(Self {
cfg,
target,
opts,
session,
closed: false,
})
}
pub async fn send(&mut self, text: &str) -> Result<(), WsError> {
self.session.send(text).await
}
pub async fn resize(&mut self, cols: u32, rows: u32) -> Result<(), WsError> {
self.session.resize(cols, rows).await
}
pub async fn recv(&mut self) -> Result<Option<String>, WsError> {
loop {
match self.session.recv().await {
Ok(Some(msg)) => return Ok(Some(msg)),
Ok(None) | Err(_) if !self.closed => {
if !self.try_reconnect().await {
return Ok(None);
}
}
Ok(None) => return Ok(None),
Err(e) => return Err(e),
}
}
}
pub async fn close(self) -> Result<(), WsError> {
self.session.close().await
}
async fn try_reconnect(&mut self) -> bool {
for i in 0..self.opts.max_retries {
tokio::time::sleep(delay_for(i, &self.opts)).await;
if let Ok(s) = ConsoleConnector::default()
.open_terminal(&self.cfg, self.target.clone())
.await
{
self.session = s;
return true;
}
}
false
}
}
pub struct ResilientVncSession {
cfg: Configuration,
target: VncTarget,
opts: RetryOptions,
session: VncSession,
closed: bool,
}
impl ResilientVncSession {
pub async fn open(
cfg: Configuration,
target: VncTarget,
opts: RetryOptions,
) -> Result<Self, WsError> {
let session = ConsoleConnector::default()
.open_vnc(&cfg, target.clone())
.await?;
Ok(Self {
cfg,
target,
opts,
session,
closed: false,
})
}
pub async fn send(&mut self, data: Vec<u8>) -> Result<(), WsError> {
self.session.send(data).await
}
pub async fn recv(&mut self) -> Result<Option<Vec<u8>>, WsError> {
loop {
match self.session.recv().await {
Ok(Some(b)) => return Ok(Some(b)),
Ok(None) | Err(_) if !self.closed => {
if !self.try_reconnect().await {
return Ok(None);
}
}
Ok(None) => return Ok(None),
Err(e) => return Err(e),
}
}
}
pub async fn close(self) -> Result<(), WsError> {
self.session.close().await
}
async fn try_reconnect(&mut self) -> bool {
for i in 0..self.opts.max_retries {
tokio::time::sleep(delay_for(i, &self.opts)).await;
if let Ok(s) = ConsoleConnector::default()
.open_vnc(&self.cfg, self.target.clone())
.await
{
self.session = s;
return true;
}
}
false
}
}
pub async fn connect_terminal_resilient(
cfg: Configuration,
target: TerminalTarget,
opts: RetryOptions,
) -> Result<ResilientTerminalSession, WsError> {
ResilientTerminalSession::open(cfg, target, opts).await
}
pub async fn connect_vnc_resilient(
cfg: Configuration,
target: VncTarget,
opts: RetryOptions,
) -> Result<ResilientVncSession, WsError> {
ResilientVncSession::open(cfg, target, opts).await
}