use crate::daemon::{DaemonCommand, DaemonState};
use crate::server::core::{CoreOptions, DaemonCore, start_daemon_core};
use choreo_proto::{ClientMessage, DaemonMessage};
use std::io;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::mpsc;
use std::thread;
use std::time::Instant;
use tracing::{error, info, warn};
#[derive(Debug, Default, Clone)]
pub struct EmbeddedOptions {}
pub struct EmbeddedLink {
pub client_tx: crossbeam_channel::Sender<ClientMessage>,
pub daemon_rx: crossbeam_channel::Receiver<DaemonMessage>,
}
pub struct EmbeddedDaemon {
core: Option<DaemonCore>,
daemon_tx: Option<mpsc::Sender<DaemonCommand>>,
global_lag: Arc<AtomicUsize>,
conn_count: Arc<AtomicUsize>,
handle_tx: crossbeam_channel::Sender<thread::JoinHandle<()>>,
handle_rx: Option<crossbeam_channel::Receiver<thread::JoinHandle<()>>>,
shut_down: bool,
}
pub fn spawn_embedded(state: DaemonState, _opts: EmbeddedOptions) -> io::Result<EmbeddedDaemon> {
info!("spawning embedded daemon core");
let core = start_daemon_core(
state,
CoreOptions {
acl: None,
config_watchers: true,
auto_exit_wake_path: None,
},
)?;
let daemon_tx = core.daemon_tx.clone();
let conn_count = Arc::clone(&core.conn_count);
let global_lag = Arc::clone(&core.global_lag);
let (handle_tx, handle_rx) = crossbeam_channel::unbounded();
info!("embedded daemon core started");
Ok(EmbeddedDaemon {
core: Some(core),
daemon_tx: Some(daemon_tx),
global_lag,
conn_count,
handle_tx,
handle_rx: Some(handle_rx),
shut_down: false,
})
}
impl EmbeddedDaemon {
pub fn connect(&self) -> io::Result<EmbeddedLink> {
let Some(slot) = crate::server::lifecycle::try_take_connection_slot(&self.conn_count)
else {
warn!(
"embedded connection rejected: at the {} concurrent-connection cap",
crate::server::lifecycle::MAX_CONCURRENT_CONNECTIONS
);
return Err(io::Error::new(
io::ErrorKind::ConnectionRefused,
"too many concurrent connections",
));
};
let (client_tx, client_rx) = crossbeam_channel::unbounded::<ClientMessage>();
let (out_tx, out_rx) = crossbeam_channel::unbounded::<DaemonMessage>();
let Some(daemon_tx) = self.daemon_tx.clone() else {
return Err(io::Error::other(
"embedded daemon is shut down; cannot connect",
));
};
let (client_id, writer, writer_rx) =
crate::server::connection::register_client_writer(&daemon_tx);
let global_lag = Arc::clone(&self.global_lag);
info!(client_id, "embedded client connecting");
crate::metrics::record_connection_accepted();
let handle = thread::spawn(move || {
let _slot = slot;
let args = crate::server::connection::EmbeddedConnArgs {
client_rx,
out_tx,
daemon_tx,
client_id,
writer,
writer_rx,
global_lag,
};
if let Err(e) = crate::server::connection::embedded_client_thread(args) {
error!(error = %e, "embedded client error");
}
});
let handle_tx = self.handle_tx.clone();
let _ = handle_tx.send(handle);
Ok(EmbeddedLink {
client_tx,
daemon_rx: out_rx,
})
}
pub fn shutdown(mut self) {
info!("embedded daemon: shutting down");
if let Some(daemon_tx) = self.daemon_tx.take() {
let _ = daemon_tx.send(DaemonCommand::BroadcastShuttingDown);
let _ = daemon_tx.send(DaemonCommand::Shutdown);
drop(daemon_tx);
}
if let Some(core) = self.core.take() {
if let Err(e) = core.cmd_handle.join() {
error!("command thread panicked during shutdown: {e:?}");
}
info!("embedded daemon: command loop joined");
}
if let Some(handle_rx) = self.handle_rx.take() {
let handles: Vec<_> = handle_rx.try_iter().collect();
let deadline = Instant::now() + crate::server::lifecycle::CONNECTION_DRAIN_GRACE;
info!(
connection_threads = handles.len(),
"draining embedded connection threads"
);
for handle in handles {
crate::server::lifecycle::join_thread_bounded(handle, deadline);
}
}
self.shut_down = true;
info!("embedded daemon: shutdown complete");
}
}
impl Drop for EmbeddedDaemon {
fn drop(&mut self) {
if !self.shut_down {
info!(
"EmbeddedDaemon dropped without shutdown(); the daemon core is left \
detached — call shutdown() for the ordered drain"
);
}
}
}