use async_trait::async_trait;
use serde_json::Value;
use std::{collections::HashMap, future, sync::Arc};
#[cfg(unix)]
use tokio::net::UnixStream;
#[cfg(windows)]
use tokio::net::windows::named_pipe::ClientOptions;
use tokio::{
io::{AsyncRead, AsyncWrite},
net::TcpStream,
sync::watch,
};
use crate::{
broker::{read_packet, write_packet},
rpc::{RpcRequest, RpcResponse},
};
trait AsyncStream: AsyncRead + AsyncWrite {}
impl<T: AsyncRead + AsyncWrite + Unpin> AsyncStream for T {}
#[async_trait]
pub trait SharedObject: Send + Sync {
async fn call(&self, method: &str, args: &Value) -> Value;
}
pub struct WorkerBuilder {
objects: HashMap<String, Arc<dyn SharedObject>>,
shutdown_rx: Option<watch::Receiver<bool>>,
}
impl WorkerBuilder {
pub fn new() -> Self {
Self {
objects: HashMap::new(),
shutdown_rx: None,
}
}
pub fn add<T>(mut self, name: &str, obj: T) -> Self
where
T: SharedObject + 'static,
{
self.objects.insert(name.to_string(), Arc::new(obj));
self
}
pub fn with_graceful_shutdown(mut self) -> (Self, watch::Sender<bool>) {
let (shutdown_tx, shutdown_rx) = watch::channel(false);
self.shutdown_rx = Some(shutdown_rx);
(self, shutdown_tx)
}
pub async fn spawn(self) -> std::io::Result<()> {
worker_supervisor(self.objects, self.shutdown_rx).await
}
}
impl Default for WorkerBuilder {
fn default() -> Self {
Self::new()
}
}
async fn worker_supervisor(
objects: HashMap<String, Arc<dyn SharedObject>>,
shutdown_rx: Option<watch::Receiver<bool>>,
) -> std::io::Result<()> {
use std::time::Duration;
loop {
let shutdown_rx = shutdown_rx.clone();
match run_worker(objects.clone(), shutdown_rx.clone()).await {
Ok(_) => {
log::warn!("Worker exited normally, ending service now...");
break;
}
Err(e) => {
log::error!("Worker has failed to connect: {e}, retrying. . .");
}
}
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Supervisor shutting down");
break;
}
_ = async {
if let Some(mut rx) = shutdown_rx {
let _ = rx.changed().await;
} else {
future::pending::<()>().await;
}
} => {
log::info!("Shutdown signal received (watch)");
break;
}
_ = tokio::time::sleep(Duration::from_secs(2)) => {}
}
}
Ok(())
}
async fn run_worker(
objects: HashMap<String, Arc<dyn SharedObject>>,
shutdown_rx: Option<watch::Receiver<bool>>,
) -> std::io::Result<()> {
let mut stream: Box<dyn AsyncStream + Send + Unpin> =
if let Ok(ip) = std::env::var("BROKER_ADDR") {
let tcp = TcpStream::connect(ip.as_str()).await?;
log::info!("Connected into TCP: {ip}");
Box::new(tcp)
} else {
#[cfg(unix)]
{
use crate::rpc::UNIX_PATH;
let unix = UnixStream::connect(UNIX_PATH).await?;
log::info!("Connected into Unix: {UNIX_PATH}");
Box::new(unix)
}
#[cfg(windows)]
{
use crate::rpc::PIPE_PATH;
loop {
let res = match ClientOptions::new().open(PIPE_PATH) {
Ok(pipe) => {
log::info!("Connected into NamedPipe: {PIPE_PATH}");
Box::new(pipe)
}
Err(e) if e.raw_os_error() == Some(231) => {
use std::time::Duration;
log::error!("All pipe instances busy, retrying...");
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
Err(e) => {
use std::time::Duration;
log::error!("Failed to connect to pipe: {}", e);
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
};
break res;
}
}
};
for name in objects.keys() {
let reg = RpcRequest::RegisterObject {
object_name: name.clone(),
};
let data = match serde_json::to_vec(®) {
Ok(d) => d,
Err(e) => {
log::error!("Failed to serialize RegisterObject for {}: {e}", name);
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e));
}
};
write_packet(&mut stream, &data).await?;
}
let mut error = None;
loop {
let shutdown_rx = shutdown_rx.clone();
tokio::select! {
_ = tokio::signal::ctrl_c() => {
log::info!("Ctrl-C received, shutting down worker loop.");
break;
}
_ = async {
if let Some(mut rx) = shutdown_rx {
let _ = rx.changed().await;
} else {
future::pending::<()>().await;
}
} => {
log::info!("Shutdown signal received (watch)");
break;
}
result = read_packet(&mut stream) => {
let buf = match result {
Ok(data) => data,
Err(err) => {
log::error!("Connection lost: {err}");
error = Some(err);
break;
}
};
if let Ok(req) = serde_json::from_slice::<RpcRequest>(&buf) {
match req {
RpcRequest::Call {
call_id,
object_name,
method,
args,
} => {
if let Some(obj) = objects.get(&object_name) {
let result = obj.call(&method, &args).await;
let cid = call_id.clone();
let resp = RpcResponse::Result {
call_id,
object_name,
value: result,
};
let resp_bytes = match serde_json::to_vec(&resp) {
Ok(b) => b,
Err(e) => {
log::error!("Failed to serialize response for call {:?}: {e}", cid);
continue;
}
};
if let Err(err) = write_packet(&mut stream, &resp_bytes).await {
log::error!("Write error (closing connection): {err}");
error = Some(err);
break;
}
} else {
log::error!("Unknown object: {object_name}");
}
}
_ => {
log::debug!("Unsupported request: {req:?}");
}
}
} else if let Ok(resp) = serde_json::from_slice::<RpcResponse>(&buf) {
log::debug!("Worker got response: {resp:?}");
} else {
log::error!("Invalid JSON value from broker");
}
}
}
}
if let Some(error) = error {
Err(error)
} else {
Ok(())
}
}