use std::fmt::Debug;
use std::sync::{
Arc as StdArc,
atomic::{AtomicUsize, Ordering},
};
use serde::Serialize;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::sync::{Notify, mpsc};
use tokio::time::{Duration, sleep};
use crate::containerisation::container_state::ContainerState;
use crate::network::serialiser;
pub struct Client<T> {
pub connection_name: String,
server_address: String,
client_rx: mpsc::Receiver<T>,
container_state: StdArc<AtomicUsize>,
container_state_notify: StdArc<Notify>,
send_retries: u8,
max_send_retries: u8,
internal_client_state: bool,
}
impl<T> Client<T>
where
T: Clone + Serialize + Debug,
{
pub fn new(
connection_name: String,
server_address: String,
client_rx: mpsc::Receiver<T>,
container_state: StdArc<AtomicUsize>,
container_state_notify: StdArc<Notify>,
max_send_retries: u8,
) -> Self {
Self {
connection_name,
server_address,
client_rx,
container_state,
container_state_notify,
send_retries: 0,
max_send_retries,
internal_client_state: true, }
}
pub async fn run(mut self) -> Result<(), Box<dyn std::error::Error>> {
self.internal_client_state = true;
self.send_retries = 0;
loop {
if self.is_closing_connection() {
break;
}
match TcpStream::connect(self.server_address.to_string()).await {
Ok(mut stream) => {
log::info!(
"{} Client connected on {}",
self.connection_name,
self.server_address
);
self.send_retries = 0;
loop {
tokio::select! {
Some(message) = self.client_rx.recv() => {
let payload = match serialiser::serialise_message(&message) {
Ok(payload) => payload,
Err(e) => {
log::error!("Failed to serialise message: {e:?} \n{message:?}");
continue; }
};
let len = (payload.len() as u32).to_be_bytes(); let mut framed_msg = Vec::with_capacity(4 + payload.len());
framed_msg.extend_from_slice(&len);
framed_msg.extend_from_slice(&payload);
match stream.write_all(&framed_msg).await {
Ok(_) => {
log::trace!("Message sent: {message:?}");
}
Err(e) => {
log::warn!("Failed to send message to {}: {e:?}", self.connection_name);
self.send_retries += 1;
if self.send_retries >= self.max_send_retries {
log::warn!("{} Client: {} failed to send message after {} retries. Closing client...",
self.connection_name, self.server_address, self.max_send_retries);
self.internal_client_state = false;
if let Err(e) = stream.shutdown().await {
log::warn!("Failed to shutdown stream: {e:?}");
}
break;
}
sleep(Duration::from_secs((2u64.pow(self.send_retries as u32)) / 2)).await;
}
}
},
_ = self.container_state_notify.notified() => {
if self.is_closing_connection() {
if let Err(e) = stream.shutdown().await {
log::warn!("Failed to shutdown stream: {e:?}");
}
break; }
}
}
}
}
Err(e) => {
log::warn!(
"{} Client: {} failed to connect: {e:?}",
self.connection_name,
self.server_address
);
self.send_retries += 1;
if self.send_retries >= self.max_send_retries {
log::warn!(
"Client {} failed to connect to Server after {} retries",
self.server_address,
self.max_send_retries
);
self.internal_client_state = false;
break;
}
sleep(Duration::from_secs(
(2u64.pow(self.send_retries as u32)) / 2,
))
.await;
}
}
}
log::info!(
"{} Client: {} closed gracefully",
self.connection_name,
self.server_address
);
Ok(())
}
fn is_closing_connection(&self) -> bool {
if ContainerState::from(self.container_state.load(Ordering::SeqCst))
== ContainerState::ShuttingDown
{
log::info!(
"{} Client ({}) has received closing instruction via ContainerState...",
self.connection_name,
self.server_address
);
true
} else if !self.internal_client_state {
log::info!(
"{} Client ({}) is closing due to connection issues...",
self.connection_name,
self.server_address
);
true
} else {
false
}
}
}