fix: add restart_again flag to DesiredStatus::Restarting

When a restart is requested while the service is already restarting
(stopped but not yet started), set restart_again so the actor will
perform another stop→start cycle after the current one completes.
This commit is contained in:
Aiden McClelland
2026-03-11 15:17:53 -06:00
parent 90b73dd320
commit 10a5bc0280
6 changed files with 37 additions and 20 deletions

View File

@@ -163,7 +163,7 @@ pub async fn set_main_status(
if prev.is_none() && status == SetMainStatusStatus::Running { if prev.is_none() && status == SetMainStatusStatus::Running {
s.as_desired_mut().map_mutate(|s| { s.as_desired_mut().map_mutate(|s| {
Ok(match s { Ok(match s {
DesiredStatus::Restarting => DesiredStatus::Running, DesiredStatus::Restarting { .. } => DesiredStatus::Running,
x => x, x => x,
}) })
})?; })?;

View File

@@ -103,7 +103,7 @@ async fn service_actor_loop<'a>(
match status { match status {
StatusInfo { StatusInfo {
desired: DesiredStatus::Running | DesiredStatus::Restarting, desired: DesiredStatus::Running | DesiredStatus::Restarting { .. },
started: None, started: None,
.. ..
} => { } => {
@@ -114,7 +114,7 @@ async fn service_actor_loop<'a>(
} }
StatusInfo { StatusInfo {
desired: desired:
DesiredStatus::Stopped | DesiredStatus::Restarting | DesiredStatus::BackingUp { .. }, DesiredStatus::Stopped | DesiredStatus::Restarting { .. } | DesiredStatus::BackingUp { .. },
started: Some(_), started: Some(_),
.. ..
} => { } => {

View File

@@ -243,12 +243,7 @@ impl ServiceMap {
PackageState::Installing(installing) PackageState::Installing(installing)
}, },
s9pk: installed_path, s9pk: installed_path,
status_info: StatusInfo { status_info: StatusInfo::default(),
error: None,
health: BTreeMap::new(),
started: None,
desired: DesiredStatus::Stopped,
},
registry, registry,
developer_key: Pem::new(developer_key), developer_key: Pem::new(developer_key),
icon, icon,

View File

@@ -38,7 +38,17 @@ impl Model<StatusInfo> {
.map_mutate(|s| Ok(Some(s.unwrap_or_else(|| Utc::now()))))?; .map_mutate(|s| Ok(Some(s.unwrap_or_else(|| Utc::now()))))?;
self.as_desired_mut().map_mutate(|s| { self.as_desired_mut().map_mutate(|s| {
Ok(match s { Ok(match s {
DesiredStatus::Restarting => DesiredStatus::Running, DesiredStatus::Restarting {
restart_again: true,
} => {
// Clear the flag but stay Restarting so actor will stop→start again
DesiredStatus::Restarting {
restart_again: false,
}
}
DesiredStatus::Restarting {
restart_again: false,
} => DesiredStatus::Running,
a => a, a => a,
}) })
})?; })?;
@@ -55,7 +65,9 @@ impl Model<StatusInfo> {
Ok(()) Ok(())
} }
pub fn restart(&mut self) -> Result<(), Error> { pub fn restart(&mut self) -> Result<(), Error> {
self.as_desired_mut().map_mutate(|s| Ok(s.restart()))?; let started = self.as_started().transpose_ref().is_some();
self.as_desired_mut()
.map_mutate(|s| Ok(s.restart(started)))?;
self.as_health_mut().ser(&Default::default())?; self.as_health_mut().ser(&Default::default())?;
Ok(()) Ok(())
} }
@@ -69,7 +81,7 @@ impl Model<StatusInfo> {
DesiredStatus::BackingUp { DesiredStatus::BackingUp {
on_complete: StartStop::Stop, on_complete: StartStop::Stop,
} => DesiredStatus::Stopped, } => DesiredStatus::Stopped,
DesiredStatus::Restarting => DesiredStatus::Running, DesiredStatus::Restarting { .. } => DesiredStatus::Running,
x => x, x => x,
}) })
})?; })?;
@@ -84,9 +96,14 @@ impl Model<StatusInfo> {
#[serde(rename_all_fields = "camelCase")] #[serde(rename_all_fields = "camelCase")]
pub enum DesiredStatus { pub enum DesiredStatus {
Stopped, Stopped,
Restarting, Restarting {
#[serde(default)]
restart_again: bool,
},
Running, Running,
BackingUp { on_complete: StartStop }, BackingUp {
on_complete: StartStop,
},
} }
impl Default for DesiredStatus { impl Default for DesiredStatus {
fn default() -> Self { fn default() -> Self {
@@ -97,7 +114,7 @@ impl DesiredStatus {
pub fn running(&self) -> bool { pub fn running(&self) -> bool {
match self { match self {
Self::Running Self::Running
| Self::Restarting | Self::Restarting { .. }
| Self::BackingUp { | Self::BackingUp {
on_complete: StartStop::Start, on_complete: StartStop::Start,
} => true, } => true,
@@ -140,10 +157,15 @@ impl DesiredStatus {
} }
} }
pub fn restart(&self) -> Self { pub fn restart(&self, started: bool) -> Self {
match self { match self {
Self::Running => Self::Restarting, Self::Running => Self::Restarting {
x => *x, // no-op: restart is meaningless in any other state restart_again: false,
},
Self::Restarting { .. } if !started => Self::Restarting {
restart_again: true,
},
x => *x,
} }
} }
} }

View File

@@ -3,6 +3,6 @@ import type { StartStop } from './StartStop'
export type DesiredStatus = export type DesiredStatus =
| { main: 'stopped' } | { main: 'stopped' }
| { main: 'restarting' } | { main: 'restarting'; restartAgain: boolean }
| { main: 'running' } | { main: 'running' }
| { main: 'backing-up'; onComplete: StartStop } | { main: 'backing-up'; onComplete: StartStop }

View File

@@ -1289,7 +1289,7 @@ export class MockApiService extends ApiService {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path, path,
value: { value: {
desired: { main: 'restarting' }, desired: { main: 'restarting', restartAgain: false },
started: null, started: null,
error: null, error: null,
health: {}, health: {},