From 3eb0093d2a9bbcc7f05e5d4347c885b989123659 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:40:12 -0600 Subject: [PATCH] feature: Adding in the stopping state (#2677) * feature: Adding in the stopping state * chore: Deal with timeout in the sigterm for main * chore: Update the timeout * Update web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> * Update web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> --------- Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> --- .../Systems/SystemForEmbassy/MainLoop.ts | 5 +- .../Systems/SystemForEmbassy/index.ts | 26 +-- .../src/Adapters/Systems/SystemForStartOs.ts | 11 +- core/startos/src/service/mod.rs | 120 +------------ .../src/service/persistent_container.rs | 4 +- core/startos/src/service/service_actor.rs | 159 ++++++++++++++++++ core/startos/src/status/mod.rs | 9 +- sdk/lib/mainFn/CommandController.ts | 14 +- sdk/lib/mainFn/Daemon.ts | 1 + sdk/lib/mainFn/Daemons.ts | 2 + sdk/lib/mainFn/HealthDaemon.ts | 9 +- sdk/lib/mainFn/index.ts | 1 + sdk/lib/osBindings/MainStatus.ts | 3 +- sdk/lib/util/inMs.test.ts | 34 ++++ sdk/lib/util/inMs.ts | 31 ++++ sdk/lib/util/index.ts | 1 + .../app-list-pkg/app-list-pkg.component.ts | 4 +- .../app-show-status.component.ts | 4 +- 18 files changed, 282 insertions(+), 156 deletions(-) create mode 100644 core/startos/src/service/service_actor.rs create mode 100644 sdk/lib/util/inMs.test.ts create mode 100644 sdk/lib/util/inMs.ts diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index 975bb52ca..f6e7614ed 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -2,7 +2,7 @@ import { polyfillEffects } from "./polyfillEffects" import { DockerProcedureContainer } from "./DockerProcedureContainer" import { SystemForEmbassy } from "." import { hostSystemStartOs } from "../../HostSystemStartOs" -import { Daemons, T, daemons } from "@start9labs/start-sdk" +import { Daemons, T, daemons, utils } from "@start9labs/start-sdk" import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon" import { Effects } from "../../../Models/Effects" @@ -58,6 +58,9 @@ export class MainLoop { currentCommand, { overlay: dockerProcedureContainer.overlay, + sigtermTimeout: utils.inMs( + this.system.manifest.main["sigterm-timeout"], + ), }, ) daemon.start() diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 3a8d4f4b2..dd2ccbab3 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -481,20 +481,20 @@ export class SystemForEmbassy implements System { private async mainStop( effects: Effects, timeoutMs: number | null, - ): Promise { - const { currentRunning } = this - this.currentRunning?.clean() - delete this.currentRunning - if (currentRunning) { - await currentRunning.clean({ - timeout: fromDuration(this.manifest.main["sigterm-timeout"]), - }) + ): Promise { + try { + const { currentRunning } = this + this.currentRunning?.clean() + delete this.currentRunning + if (currentRunning) { + await currentRunning.clean({ + timeout: utils.inMs(this.manifest.main["sigterm-timeout"]), + }) + } + return + } finally { + await effects.setMainStatus({ status: "stopped" }) } - const durationValue = duration( - fromDuration(this.manifest.main["sigterm-timeout"]), - "s", - ) - return durationValue } private async createBackup( effects: Effects, diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index 84bf57302..2c455dc42 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -139,10 +139,13 @@ export class SystemForStartOs implements System { return } case "/main/stop": { - if (this.onTerm) await this.onTerm() - await effects.setMainStatus({ status: "stopped" }) - delete this.onTerm - return duration(30, "s") + try { + if (this.onTerm) await this.onTerm() + delete this.onTerm + return + } finally { + await effects.setMainStatus({ status: "stopped" }) + } } case "/config/set": { const input = options.input as any // TODO diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index d78df6c79..9103b70a1 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -10,6 +10,7 @@ use models::{HealthCheckId, PackageId, ProcedureName}; use persistent_container::PersistentContainer; use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; use serde::{Deserialize, Serialize}; +use service_actor::ServiceActor; use start_stop::StartStop; use tokio::sync::Notify; use ts_rs::TS; @@ -45,6 +46,7 @@ mod dependencies; pub mod persistent_container; mod properties; mod rpc; +mod service_actor; pub mod service_effect_handler; pub mod service_map; mod start_stop; @@ -482,124 +484,6 @@ impl ServiceActorSeed { }); } } -#[derive(Clone)] -struct ServiceActor(Arc); - -impl Actor for ServiceActor { - fn init(&mut self, jobs: &BackgroundJobQueue) { - let seed = self.0.clone(); - jobs.add_job(async move { - let id = seed.id.clone(); - let mut current = seed.persistent_container.state.subscribe(); - - loop { - let kinds = current.borrow().kinds(); - - if let Err(e) = async { - let main_status = match ( - kinds.transition_state, - kinds.desired_state, - kinds.running_status, - ) { - (Some(TransitionKind::Restarting), StartStop::Stop, Some(_)) => { - seed.persistent_container.stop().await?; - MainStatus::Restarting - } - (Some(TransitionKind::Restarting), StartStop::Start, _) => { - seed.persistent_container.start().await?; - MainStatus::Restarting - } - (Some(TransitionKind::Restarting), _, _) => MainStatus::Restarting, - (Some(TransitionKind::Restoring), _, _) => MainStatus::Restoring, - (Some(TransitionKind::BackingUp), StartStop::Stop, Some(status)) => { - seed.persistent_container.stop().await?; - MainStatus::BackingUp { - started: Some(status.started), - health: status.health.clone(), - } - } - (Some(TransitionKind::BackingUp), StartStop::Start, _) => { - seed.persistent_container.start().await?; - MainStatus::BackingUp { - started: None, - health: OrdMap::new(), - } - } - (Some(TransitionKind::BackingUp), _, _) => MainStatus::BackingUp { - started: None, - health: OrdMap::new(), - }, - (None, StartStop::Stop, None) => MainStatus::Stopped, - (None, StartStop::Stop, Some(_)) => MainStatus::Stopping { - timeout: seed.persistent_container.stop().await?.into(), - }, - (None, StartStop::Start, Some(status)) => MainStatus::Running { - started: status.started, - health: status.health.clone(), - }, - (None, StartStop::Start, None) => { - seed.persistent_container.start().await?; - MainStatus::Starting - } - }; - seed.ctx - .db - .mutate(|d| { - if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) - { - let previous = i.as_status().as_main().de()?; - let previous_health = previous.health(); - let previous_started = previous.started(); - let mut main_status = main_status; - match &mut main_status { - &mut MainStatus::Running { ref mut health, .. } - | &mut MainStatus::BackingUp { ref mut health, .. } => { - *health = previous_health.unwrap_or(health).clone(); - } - _ => (), - }; - match &mut main_status { - MainStatus::Running { - ref mut started, .. - } => { - *started = previous_started.unwrap_or(*started); - } - MainStatus::BackingUp { - ref mut started, .. - } => { - *started = previous_started.map(Some).unwrap_or(*started); - } - _ => (), - }; - i.as_status_mut().as_main_mut().ser(&main_status)?; - } - Ok(()) - }) - .await?; - - Ok::<_, Error>(()) - } - .await - { - tracing::error!("error synchronizing state of service: {e}"); - tracing::debug!("{e:?}"); - - seed.synchronized.notify_waiters(); - - tracing::error!("Retrying in {}s...", SYNC_RETRY_COOLDOWN_SECONDS); - tokio::time::sleep(Duration::from_secs(SYNC_RETRY_COOLDOWN_SECONDS)).await; - continue; - } - - seed.synchronized.notify_waiters(); - - tokio::select! { - _ = current.changed() => (), - } - } - }) - } -} #[derive(Deserialize, Serialize, Parser, TS)] pub struct ConnectParams { diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index 11cc237d3..e8c504a92 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -441,11 +441,11 @@ impl PersistentContainer { } #[instrument(skip_all)] - pub async fn stop(&self) -> Result { + pub async fn stop(&self) -> Result<(), Error> { let timeout: Option = self .execute(Guid::new(), ProcedureName::StopMain, Value::Null, None) .await?; - Ok(timeout.map(|a| *a).unwrap_or(Duration::from_secs(30))) + Ok(()) } #[instrument(skip_all)] diff --git a/core/startos/src/service/service_actor.rs b/core/startos/src/service/service_actor.rs new file mode 100644 index 000000000..e6e1c4ede --- /dev/null +++ b/core/startos/src/service/service_actor.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; +use std::time::Duration; + +use imbl::OrdMap; +use models::PackageId; + +use super::start_stop::StartStop; + +use crate::prelude::*; +use crate::service::transition::TransitionKind; +use crate::service::SYNC_RETRY_COOLDOWN_SECONDS; +use crate::status::MainStatus; +use crate::util::actor::background::BackgroundJobQueue; +use crate::util::actor::Actor; + +use super::ServiceActorSeed; + +#[derive(Clone)] +pub(super) struct ServiceActor(pub(super) Arc); + +enum ServiceActorLoopNext { + Wait, + DontWait, +} + +impl Actor for ServiceActor { + fn init(&mut self, jobs: &BackgroundJobQueue) { + let seed = self.0.clone(); + jobs.add_job(async move { + let id = seed.id.clone(); + let mut current = seed.persistent_container.state.subscribe(); + + loop { + match service_actor_loop(¤t, &seed, &id).await { + ServiceActorLoopNext::Wait => tokio::select! { + _ = current.changed() => (), + }, + ServiceActorLoopNext::DontWait => (), + } + } + }) + } +} + +async fn service_actor_loop( + current: &tokio::sync::watch::Receiver, + seed: &Arc, + id: &PackageId, +) -> ServiceActorLoopNext { + let kinds = current.borrow().kinds(); + if let Err(e) = async { + let main_status = match ( + kinds.transition_state, + kinds.desired_state, + kinds.running_status, + ) { + (Some(TransitionKind::Restarting), StartStop::Stop, Some(_)) => { + seed.persistent_container.stop().await?; + MainStatus::Restarting + } + (Some(TransitionKind::Restarting), StartStop::Start, _) => { + seed.persistent_container.start().await?; + MainStatus::Restarting + } + (Some(TransitionKind::Restarting), _, _) => MainStatus::Restarting, + (Some(TransitionKind::Restoring), _, _) => MainStatus::Restoring, + (Some(TransitionKind::BackingUp), StartStop::Stop, Some(status)) => { + seed.persistent_container.stop().await?; + MainStatus::BackingUp { + started: Some(status.started), + health: status.health.clone(), + } + } + (Some(TransitionKind::BackingUp), StartStop::Start, _) => { + seed.persistent_container.start().await?; + MainStatus::BackingUp { + started: None, + health: OrdMap::new(), + } + } + (Some(TransitionKind::BackingUp), _, _) => MainStatus::BackingUp { + started: None, + health: OrdMap::new(), + }, + (None, StartStop::Stop, None) => MainStatus::Stopped, + (None, StartStop::Stop, Some(_)) => { + let task_seed = seed.clone(); + seed.ctx + .db + .mutate(|d| { + if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) { + i.as_status_mut().as_main_mut().ser(&MainStatus::Stopping)?; + } + Ok(()) + }) + .await?; + task_seed.persistent_container.stop().await?; + MainStatus::Stopped + } + (None, StartStop::Start, Some(status)) => MainStatus::Running { + started: status.started, + health: status.health.clone(), + }, + (None, StartStop::Start, None) => { + seed.persistent_container.start().await?; + MainStatus::Starting + } + }; + seed.ctx + .db + .mutate(|d| { + if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) { + let previous = i.as_status().as_main().de()?; + let previous_health = previous.health(); + let previous_started = previous.started(); + let mut main_status = main_status; + match &mut main_status { + &mut MainStatus::Running { ref mut health, .. } + | &mut MainStatus::BackingUp { ref mut health, .. } => { + *health = previous_health.unwrap_or(health).clone(); + } + _ => (), + }; + match &mut main_status { + MainStatus::Running { + ref mut started, .. + } => { + *started = previous_started.unwrap_or(*started); + } + MainStatus::BackingUp { + ref mut started, .. + } => { + *started = previous_started.map(Some).unwrap_or(*started); + } + _ => (), + }; + i.as_status_mut().as_main_mut().ser(&main_status)?; + } + Ok(()) + }) + .await?; + + Ok::<_, Error>(()) + } + .await + { + tracing::error!("error synchronizing state of service: {e}"); + tracing::debug!("{e:?}"); + + seed.synchronized.notify_waiters(); + + tracing::error!("Retrying in {}s...", SYNC_RETRY_COOLDOWN_SECONDS); + tokio::time::sleep(Duration::from_secs(SYNC_RETRY_COOLDOWN_SECONDS)).await; + return ServiceActorLoopNext::DontWait; + } + seed.synchronized.notify_waiters(); + + ServiceActorLoopNext::Wait +} diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index 520fe5089..c1d3a36ad 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, sync::Arc}; use chrono::{DateTime, Utc}; use imbl::OrdMap; @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use ts_rs::TS; use self::health_check::HealthCheckId; -use crate::prelude::*; use crate::status::health_check::HealthCheckResult; +use crate::{prelude::*, util::GeneralGuard}; pub mod health_check; #[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] @@ -26,10 +26,7 @@ pub enum MainStatus { Stopped, Restarting, Restoring, - #[serde(rename_all = "camelCase")] - Stopping { - timeout: crate::util::serde::Duration, - }, + Stopping, Starting, #[serde(rename_all = "camelCase")] Running { diff --git a/sdk/lib/mainFn/CommandController.ts b/sdk/lib/mainFn/CommandController.ts index 264574f7c..3af01e915 100644 --- a/sdk/lib/mainFn/CommandController.ts +++ b/sdk/lib/mainFn/CommandController.ts @@ -1,3 +1,4 @@ +import { DEFAULT_SIGTERM_TIMEOUT } from "." import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk" import { SDKManifest } from "../manifest/ManifestTypes" import { Effects, ImageId, ValidIfNoStupidEscape } from "../types" @@ -10,6 +11,7 @@ export class CommandController { readonly runningAnswer: Promise, readonly overlay: Overlay, readonly pid: number | undefined, + readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, ) {} static of() { return async ( @@ -20,6 +22,8 @@ export class CommandController { }, command: ValidIfNoStupidEscape | [string, ...string[]], options: { + // Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms + sigtermTimeout?: number mounts?: { path: string; options: MountOptions }[] overlay?: Overlay env?: @@ -67,10 +71,14 @@ export class CommandController { const pid = childProcess.pid - return new CommandController(answer, overlay, pid) + return new CommandController(answer, overlay, pid, options.sigtermTimeout) } } - async wait() { + async wait(timeout: number = NO_TIMEOUT) { + if (timeout > 0) + setTimeout(() => { + this.term() + }, timeout) try { return await this.runningAnswer } finally { @@ -82,7 +90,7 @@ export class CommandController { await this.overlay.destroy().catch((_) => {}) } } - async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) { + async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) { if (this.pid === undefined) return try { await cpExecFile("pkill", [ diff --git a/sdk/lib/mainFn/Daemon.ts b/sdk/lib/mainFn/Daemon.ts index c48865f94..90882418c 100644 --- a/sdk/lib/mainFn/Daemon.ts +++ b/sdk/lib/mainFn/Daemon.ts @@ -34,6 +34,7 @@ export class Daemon { user?: string | undefined onStdout?: (x: Buffer) => void onStderr?: (x: Buffer) => void + sigtermTimeout?: number }, ) => { const startCommand = () => diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/lib/mainFn/Daemons.ts index 059d148ab..fbda547aa 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/lib/mainFn/Daemons.ts @@ -44,6 +44,7 @@ type DaemonsParams< env?: Record ready: Ready requires: Exclude[] + sigtermTimeout?: number } type ErrorDuplicateId = `The id '${Id}' is already used` @@ -136,6 +137,7 @@ export class Daemons { this.ids, options.ready, this.effects, + options.sigtermTimeout, ) const daemons = this.daemons.concat(daemon) const ids = [...this.ids, id] as (Ids | Id)[] diff --git a/sdk/lib/mainFn/HealthDaemon.ts b/sdk/lib/mainFn/HealthDaemon.ts index 48f3fab55..84865e59b 100644 --- a/sdk/lib/mainFn/HealthDaemon.ts +++ b/sdk/lib/mainFn/HealthDaemon.ts @@ -3,6 +3,7 @@ import { defaultTrigger } from "../trigger/defaultTrigger" import { Ready } from "./Daemons" import { Daemon } from "./Daemon" import { Effects } from "../types" +import { DEFAULT_SIGTERM_TIMEOUT } from "." const oncePromise = () => { let resolve: (value: T) => void @@ -32,6 +33,7 @@ export class HealthDaemon { readonly ids: string[], readonly ready: Ready, readonly effects: Effects, + readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, ) { this.updateStatus() this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus())) @@ -46,7 +48,12 @@ export class HealthDaemon { this.#running = false this.#healthCheckCleanup?.() - await this.daemon.then((d) => d.stop(termOptions)) + await this.daemon.then((d) => + d.stop({ + timeout: this.sigtermTimeout, + ...termOptions, + }), + ) } /** Want to add another notifier that the health might have changed */ diff --git a/sdk/lib/mainFn/index.ts b/sdk/lib/mainFn/index.ts index 3da57d32f..c4f74764c 100644 --- a/sdk/lib/mainFn/index.ts +++ b/sdk/lib/mainFn/index.ts @@ -7,6 +7,7 @@ import "./Daemons" import { SDKManifest } from "../manifest/ManifestTypes" import { MainEffects } from "../StartSdk" +export const DEFAULT_SIGTERM_TIMEOUT = 30_000 /** * Used to ensure that the main function is running with the valid proofs. * We first do the folowing order of things diff --git a/sdk/lib/osBindings/MainStatus.ts b/sdk/lib/osBindings/MainStatus.ts index 1b9d8482e..6acdce14a 100644 --- a/sdk/lib/osBindings/MainStatus.ts +++ b/sdk/lib/osBindings/MainStatus.ts @@ -1,5 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Duration } from "./Duration" import type { HealthCheckId } from "./HealthCheckId" import type { HealthCheckResult } from "./HealthCheckResult" @@ -7,7 +6,7 @@ export type MainStatus = | { status: "stopped" } | { status: "restarting" } | { status: "restoring" } - | { status: "stopping"; timeout: Duration } + | { status: "stopping" } | { status: "starting" } | { status: "running" diff --git a/sdk/lib/util/inMs.test.ts b/sdk/lib/util/inMs.test.ts new file mode 100644 index 000000000..fbf71bf2c --- /dev/null +++ b/sdk/lib/util/inMs.test.ts @@ -0,0 +1,34 @@ +import { inMs } from "./inMs" + +describe("inMs", () => { + test("28.001s", () => { + expect(inMs("28.001s")).toBe(28001) + }) + test("28.123s", () => { + expect(inMs("28.123s")).toBe(28123) + }) + test(".123s", () => { + expect(inMs(".123s")).toBe(123) + }) + test("123ms", () => { + expect(inMs("123ms")).toBe(123) + }) + test("1h", () => { + expect(inMs("1h")).toBe(3600000) + }) + test("1m", () => { + expect(inMs("1m")).toBe(60000) + }) + test("1m", () => { + expect(inMs("1d")).toBe(1000 * 60 * 60 * 24) + }) + test("123", () => { + expect(() => inMs("123")).toThrowError("Invalid time format: 123") + }) + test("123 as number", () => { + expect(inMs(123)).toBe(123) + }) + test.only("undefined", () => { + expect(inMs(undefined)).toBe(undefined) + }) +}) diff --git a/sdk/lib/util/inMs.ts b/sdk/lib/util/inMs.ts new file mode 100644 index 000000000..547fb8bea --- /dev/null +++ b/sdk/lib/util/inMs.ts @@ -0,0 +1,31 @@ +import { DEFAULT_SIGTERM_TIMEOUT } from "../mainFn" + +const matchTimeRegex = /^\s*(\d+)?(\.\d+)?\s*(ms|s|m|h|d)/ + +const unitMultiplier = (unit?: string) => { + if (!unit) return 1 + if (unit === "ms") return 1 + if (unit === "s") return 1000 + if (unit === "m") return 1000 * 60 + if (unit === "h") return 1000 * 60 * 60 + if (unit === "d") return 1000 * 60 * 60 * 24 + throw new Error(`Invalid unit: ${unit}`) +} +const digitsMs = (digits: string | null, multiplier: number) => { + if (!digits) return 0 + const value = parseInt(digits.slice(1)) + const divideBy = multiplier / Math.pow(10, digits.length - 1) + return Math.round(value * divideBy) +} +export const inMs = (time?: string | number) => { + if (typeof time === "number") return time + if (!time) return undefined + const matches = time.match(matchTimeRegex) + if (!matches) throw new Error(`Invalid time format: ${time}`) + const [_, leftHandSide, digits, unit] = matches + const multiplier = unitMultiplier(unit) + const firstValue = parseInt(leftHandSide || "0") * multiplier + const secondValue = digitsMs(digits, multiplier) + + return firstValue + secondValue +} diff --git a/sdk/lib/util/index.ts b/sdk/lib/util/index.ts index 63629dfaa..d4427adb0 100644 --- a/sdk/lib/util/index.ts +++ b/sdk/lib/util/index.ts @@ -12,3 +12,4 @@ export { addressHostToUrl } from "./getServiceInterface" export { hostnameInfoToAddress } from "./Hostname" export * from "./typeHelpers" export { getDefaultString } from "./getDefaultString" +export { inMs } from "./inMs" diff --git a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 35bf99f8f..4df818956 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -24,9 +24,7 @@ export class AppListPkgComponent { } get sigtermTimeout(): string | null { - return this.pkgMainStatus.status === 'stopping' - ? this.pkgMainStatus.timeout - : null + return this.pkgMainStatus.status === 'stopping' ? '30s' : null // @dr-bonez TODO } launchUi(e: Event, interfaces: PackageDataEntry['serviceInterfaces']): void { diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts index 30657ae01..186bbe39a 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -76,9 +76,7 @@ export class AppShowStatusComponent { } get sigtermTimeout(): string | null { - return this.pkgStatus?.main.status === 'stopping' - ? this.pkgStatus.main.timeout - : null + return this.pkgStatus?.main.status === 'stopping' ? '30s' : null // @dr-bonez TODO } launchUi(interfaces: PackageDataEntry['serviceInterfaces']): void {