fix(maestro): serialize structured shutdown
This commit is contained in:
@@ -127,6 +127,8 @@ pub enum SessionError {
|
||||
/// a stray keystroke on an attach socket, which is dropped.
|
||||
#[error("session is no longer running: {0}")]
|
||||
NotRunning(Uuid),
|
||||
#[error("daemon is shutting down")]
|
||||
ShuttingDown,
|
||||
/// The kill was refused (#79): the session's worktree holds unsaved work.
|
||||
/// Nothing happened — the session keeps running and the tree is untouched,
|
||||
/// so it stays the operator's handle on their own unfinished work.
|
||||
@@ -144,6 +146,7 @@ impl SessionError {
|
||||
SessionError::NotFound(_) => "not_found",
|
||||
SessionError::InvalidEnv(_) => "invalid_env",
|
||||
SessionError::NotRunning(_) => "not_running",
|
||||
SessionError::ShuttingDown => "shutting_down",
|
||||
SessionError::KillGuarded(_) => "kill_guarded",
|
||||
};
|
||||
let unsaved_work = match self {
|
||||
@@ -198,6 +201,9 @@ struct Inner {
|
||||
/// Once daemon shutdown starts, it owns structured-session finalization.
|
||||
/// Background passes must not classify those deliberate exits as natural.
|
||||
shutting_down: AtomicBool,
|
||||
/// Serializes structured creation with shutdown so every adapter child is
|
||||
/// either rejected or included in shutdown's final drain.
|
||||
structured_lifecycle: Mutex<()>,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
@@ -241,6 +247,7 @@ impl SessionManager {
|
||||
viewports: Mutex::new(Viewports::new()),
|
||||
next_viewport: AtomicU64::new(0),
|
||||
shutting_down: AtomicBool::new(false),
|
||||
structured_lifecycle: Mutex::new(()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -356,6 +363,7 @@ impl SessionManager {
|
||||
if self.inner.shutting_down.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
let _lifecycle = self.inner.structured_lifecycle.lock().await;
|
||||
let children: Vec<(Uuid, Arc<AcpSession>, Option<watch::Receiver<bool>>)> = {
|
||||
let sessions = self.inner.sessions.lock().await;
|
||||
sessions
|
||||
@@ -465,6 +473,17 @@ impl SessionManager {
|
||||
|
||||
/// Spawn a new session. On success returns the freshly-created `Session`.
|
||||
pub async fn create(&self, req: CreateSessionRequest) -> Result<Session, SessionError> {
|
||||
if req.view == SessionView::Structured {
|
||||
let _lifecycle = self.inner.structured_lifecycle.lock().await;
|
||||
if self.inner.shutting_down.load(Ordering::Acquire) {
|
||||
return Err(SessionError::ShuttingDown);
|
||||
}
|
||||
return self.create_inner(req).await;
|
||||
}
|
||||
self.create_inner(req).await
|
||||
}
|
||||
|
||||
async fn create_inner(&self, req: CreateSessionRequest) -> Result<Session, SessionError> {
|
||||
let (effective_cwd, worktree_path, worktree_base_repo) = match &req.worktree {
|
||||
Some(spec) => {
|
||||
let WorktreeSpec {
|
||||
|
||||
Reference in New Issue
Block a user