use std::sync::Arc;
use std::time::Duration;
use super::DaemonState;
use crate::session::wire::{DaemonFrame, RefuseReason};
use crate::session::{SessionCommand, SessionId, SessionLifecycle, TransportError};
pub const PIN_BUDGET: Duration = Duration::from_secs(2);
pub const PIN_UNPARK_BUDGET: Duration = Duration::from_secs(10);
const PIN_POLL: Duration = Duration::from_millis(20);
pub async fn pin_session(state: &Arc<DaemonState>, id: &SessionId, on: bool) -> DaemonFrame {
if state.reloading.load(std::sync::atomic::Ordering::SeqCst) {
return DaemonFrame::Refused { reason: RefuseReason::Busy, message: "daemon reloading; retry in a moment".into() };
}
let live = state.attach(id);
if live.as_ref().is_none_or(|h| h.meta().locked_by.is_some() && !on) {
match super::pins::pin_offline(state, id.as_str(), on).await {
Some(Ok(())) => return DaemonFrame::Pinned { session_id: id.clone(), on },
Some(Err(message)) => return DaemonFrame::Error { session_id: Some(id.clone()), message },
None => {}
}
}
let Some(handle) = live else {
return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("no live session {id}") };
};
let sid = handle.id.clone();
if let Some(holder) = handle.meta().locked_by.clone() {
return DaemonFrame::Refused {
reason: RefuseReason::Busy,
message: format!(
"session {sid} is live in another process ({holder}), not in this daemon, so there is nothing \
to pin here. Quit it there, then load it into the daemon with `synaps --attach --continue {sid}` \
and pin it with /pin or `synaps daemon pin {sid}`"
),
};
}
let budget = if on && handle.lifecycle() == SessionLifecycle::Parked { PIN_UNPARK_BUDGET } else { PIN_BUDGET };
let deadline = tokio::time::Instant::now() + budget;
loop {
match handle.send(SessionCommand::KeepWarm { on }).await {
Ok(()) => break,
Err(TransportError::Backpressure) if tokio::time::Instant::now() < deadline => {
tokio::time::sleep(PIN_POLL).await;
}
Err(TransportError::Backpressure) => {
return DaemonFrame::Error {
session_id: Some(sid),
message: "pin: the session's command queue stayed full; retry".into(),
};
}
Err(_) => {
return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("session {sid} has ended") };
}
}
}
while handle.pinned() != on {
if !handle.is_alive() {
return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("session {sid} has ended") };
}
if tokio::time::Instant::now() >= deadline {
return DaemonFrame::Error {
session_id: Some(sid),
message: format!("pin: the session did not apply it within {} s", budget.as_secs()),
};
}
tokio::time::sleep(PIN_POLL).await;
}
DaemonFrame::Pinned { session_id: sid, on }
}