pub mod client;
pub mod protocol;
#[cfg(unix)]
pub mod server;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::ipc::client::RemoteCallError;
use crate::ipc::protocol::Request;
use crate::model::core_config::Config;
use crate::model::errors::{LbErrKind, LbResult};
use crate::service::logging;
use crate::{Lb, LocalLb};
pub const SOCKET_FILENAME: &str = "lb.sock";
pub fn socket_path(writeable_path: impl AsRef<Path>) -> PathBuf {
writeable_path.as_ref().join(SOCKET_FILENAME)
}
impl Lb {
pub fn is_local(&self) -> bool {
self.local.get().is_some()
}
pub fn try_local(&self) -> Option<LocalLb> {
self.local.get().cloned()
}
#[cfg_attr(not(unix), allow(dead_code))]
pub(crate) async fn recover(&self) -> LbResult<LocalLb> {
if let Some(local) = self.local.get() {
return Ok(local.clone());
}
let loc = match LocalLb::init(self.config.clone()).await {
Ok(l) => l,
Err(e) => {
if let Some(local) = self.local.get() {
return Ok(local.clone());
}
return Err(e);
}
};
logging::init(&loc.config)?;
spawn_host(loc.clone());
match self.local.set(loc.clone()) {
Ok(()) => Ok(loc),
Err(_) => Ok(self.local.get().unwrap().clone()),
}
}
pub async fn call<Out>(&self, req: Request) -> LbResult<Out>
where
Out: serde::de::DeserializeOwned,
{
let remote = self
.remote
.as_ref()
.expect("Lb::call: remote must be set when local is unset");
match remote.try_call::<Out>(req.clone()).await {
Ok(v) => Ok(v),
#[cfg(unix)]
Err(RemoteCallError::HostUnavailable) => {
let local = self.recover().await?;
let bytes = server::dispatch(&local, req).await;
let result: LbResult<Out> = bincode::deserialize(&bytes).map_err(|e| {
LbErrKind::Unexpected(format!("local dispatch deserialize: {e}"))
})?;
result
}
#[cfg(not(unix))]
Err(RemoteCallError::HostUnavailable) => {
let _ = req;
unreachable!("HostUnavailable cannot occur on non-unix targets")
}
Err(RemoteCallError::Other(e)) => Err(e),
}
}
}
pub fn spawn_host(lb: LocalLb) {
#[cfg(not(unix))]
{
let _ = lb;
}
#[cfg(unix)]
{
let socket = socket_path(&lb.config.writeable_path);
if socket.exists() {
let _ = std::fs::remove_file(&socket);
}
match tokio::net::UnixListener::bind(&socket) {
Ok(listener) => {
tokio::spawn(server::serve(listener, lb));
}
Err(err) => {
tracing::warn!(?err, "failed to bind ipc listener; guests cannot attach");
}
}
}
}
pub async fn connect_guest(config: &Config) -> Option<Arc<client::RemoteLb>> {
#[cfg(not(unix))]
{
let _ = config;
None
}
#[cfg(unix)]
{
let socket = socket_path(&config.writeable_path);
if !socket.exists() {
return None;
}
let mut attempts: u32 = 0;
let mut delay = std::time::Duration::from_millis(10);
loop {
match client::RemoteLb::connect(&socket).await {
Ok(c) => return Some(c),
Err(_) if attempts < 10 => {
attempts += 1;
tokio::time::sleep(delay).await;
delay = std::cmp::min(delay * 2, std::time::Duration::from_millis(500));
}
Err(_) => return None,
}
}
}
}