use crate::session::dispatch::DispatchState;
use anyhow::{Context, Result};
use onlyne_adapter::AdapterIo;
use onlyne_layout::local_socket::prelude::TokioListener;
use onlyne_layout::{LocalListener, RoleWorkspace, SocketEndpoint, bind_socket, connect_local};
use onlyne_proto::{AdapterMsg, HelloArgs, HostOp, MountKind, PROTOCOL_VERSION, PluginOp};
use std::path::{Path, PathBuf};
use std::time::Duration;
#[derive(Clone)]
pub struct AdapterSocket {
pub workspace: PathBuf,
pub role: String,
pub cluster: String,
pub server: String,
pub dispatch: DispatchState,
}
impl AdapterSocket {
pub fn path(&self) -> PathBuf {
RoleWorkspace::resolve(&self.workspace).socket_path()
}
#[allow(clippy::unused_async)]
pub async fn bind(&self) -> Result<(LocalListener, SocketEndpoint)> {
let layout = RoleWorkspace::resolve(&self.workspace);
let (listener, endpoint) =
bind_socket(layout.root(), &layout.run_dir()).with_context(|| {
format!(
"bind the workspace socket {}",
layout.socket_path_natural().display(),
)
})?;
if endpoint.short() {
let natural = endpoint.natural();
tracing::warn!(
canonical = %natural.display(),
canonical_bytes = natural.as_os_str().len(),
served = %endpoint.actual().display(),
marker = %endpoint.marker().display(),
"adapter socket moved to the short path"
);
} else {
tracing::info!(socket = %endpoint.actual().display(), "adapter socket serving");
}
Ok((listener, endpoint))
}
pub async fn accept_loop(&self, listener: LocalListener) -> Result<()> {
loop {
match listener.accept().await {
Ok(stream) => {
let this = self.clone();
tokio::spawn(async move {
if let Err(err) = this.connection(stream).await {
tracing::debug!(error = %err, "adapter connection closed");
}
});
}
Err(error) => {
tracing::error!(
error = %error,
kind = ?error.kind(),
"adapter socket accept failed; retrying"
);
tokio::time::sleep(ACCEPT_RETRY_PAUSE).await;
}
}
}
}
pub async fn serve(self) -> Result<()> {
let (listener, _) = self.bind().await?;
self.accept_loop(listener).await
}
}
pub const PROBE_TIMEOUT: Duration = Duration::from_secs(5);
pub const ACCEPT_RETRY_PAUSE: Duration = Duration::from_millis(100);
pub async fn server_link_state(socket: &Path) -> Option<bool> {
let stream = connect_local(socket).await.ok()?;
let io = AdapterIo::new(stream, PROBE_TIMEOUT, PROBE_TIMEOUT);
let hello = HelloArgs {
protocol: PROTOCOL_VERSION,
plugin: "onlyne-client".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
kind: MountKind::Admin,
capabilities: Vec::new(),
mount: None,
};
let body = io
.request(AdapterMsg::Plugin(PluginOp::Hello(hello)))
.await
.ok()?;
if !body.ok {
return Some(false);
}
let ack = body
.data
.and_then(|value| serde_json::from_value::<HostOp>(value).ok());
Some(matches!(
ack,
Some(HostOp::Welcome(ack)) if ack.server.connected
))
}
pub async fn stale_socket_removed(path: &Path) -> Result<()> {
match tokio::fs::remove_file(path).await {
Ok(()) => {
tracing::info!(socket = %path.display(), "removed stale adapter socket");
Ok(())
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error).with_context(|| format!("remove stale socket {}", path.display())),
}
}