250 lines
11 KiB
Rust
250 lines
11 KiB
Rust
// file: crates/ksp-app-store-desk/src/store_runtime.rs
|
|
// version: 1
|
|
|
|
//! Composite-selected Store readiness, status and bounded shutdown lifecycle owned by Store Desk.
|
|
|
|
/// Startup result for Store, including safe diagnostics when the desktop shell remains available without an opened runtime.
|
|
pub(crate) struct StoreStartup {
|
|
backend_kind: std::option::Option<String>,
|
|
diagnostic: std::option::Option<crate::CommandErrorDto>,
|
|
network: std::option::Option<String>,
|
|
profile_id: std::option::Option<String>,
|
|
runtime: std::option::Option<StoreRuntime>,
|
|
}
|
|
|
|
impl StoreStartup {
|
|
/// Builds a fresh backend-neutral runtime/health projection for the Overview screen.
|
|
pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
|
|
let runtime = self.runtime.as_ref();
|
|
return match runtime {
|
|
std::option::Option::Some(value) => value.status().await,
|
|
std::option::Option::None => crate::StoreRuntimeStatusDto {
|
|
backend_kind: self.backend_kind.clone(),
|
|
diagnostic: self.diagnostic.clone(),
|
|
health_state: "unavailable".to_owned(),
|
|
migration_version_decimal: std::option::Option::None,
|
|
network: self.network.clone(),
|
|
pending_migration_count: 0,
|
|
pool_available: 0,
|
|
pool_capacity: 0,
|
|
pool_size: 0,
|
|
pool_waiting: 0,
|
|
profile_id: self.profile_id.clone(),
|
|
store_open: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Closes the retained Store runtime if startup reached the physical Store-open phase.
|
|
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
|
|
let runtime = self.runtime.as_ref();
|
|
return match runtime {
|
|
std::option::Option::Some(value) => value.close().await,
|
|
std::option::Option::None => std::result::Result::Ok(()),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Executable Store runtime retained behind an async read/write lock so read-only Desk queries may coexist while shutdown remains exclusive.
|
|
pub(crate) struct StoreRuntime {
|
|
profile_id: String,
|
|
store: tokio::sync::RwLock<std::option::Option<ksp_store_lib::Store>>,
|
|
}
|
|
|
|
impl StoreRuntime {
|
|
/// Builds one fresh safe Store status from the retained facade without exposing backend connection details.
|
|
pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
|
|
let locked = self.store.read().await;
|
|
let store = locked.as_ref();
|
|
let store = match store {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return crate::StoreRuntimeStatusDto {
|
|
backend_kind: std::option::Option::None,
|
|
diagnostic: std::option::Option::None,
|
|
health_state: "closed".to_owned(),
|
|
migration_version_decimal: std::option::Option::None,
|
|
network: std::option::Option::None,
|
|
pending_migration_count: 0,
|
|
pool_available: 0,
|
|
pool_capacity: 0,
|
|
pool_size: 0,
|
|
pool_waiting: 0,
|
|
profile_id: std::option::Option::Some(self.profile_id.clone()),
|
|
store_open: false,
|
|
};
|
|
},
|
|
};
|
|
let runtime = store.runtime_snapshot();
|
|
let health = store.health().await;
|
|
let health_state = store_health_state_code(health.state()).to_owned();
|
|
let diagnostic = non_ready_health_diagnostic(&health);
|
|
return crate::StoreRuntimeStatusDto {
|
|
backend_kind: std::option::Option::Some(runtime.backend_kind().code().to_owned()),
|
|
diagnostic,
|
|
health_state,
|
|
migration_version_decimal: health.migration_version().map(|value| return value.to_string()),
|
|
network: std::option::Option::Some(runtime.network().as_str().to_owned()),
|
|
pending_migration_count: health.pending_migration_count(),
|
|
pool_available: runtime.pool_available(),
|
|
pool_capacity: runtime.pool_capacity(),
|
|
pool_size: runtime.pool_size(),
|
|
pool_waiting: runtime.pool_waiting(),
|
|
profile_id: std::option::Option::Some(self.profile_id.clone()),
|
|
store_open: true,
|
|
};
|
|
}
|
|
|
|
/// Explicitly closes the Store exactly once through its backend-neutral facade after all read guards have drained.
|
|
pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
|
|
let mut locked = self.store.write().await;
|
|
let store = locked.take();
|
|
drop(locked);
|
|
let store = match store {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Ok(()),
|
|
};
|
|
let snapshot = store.runtime_snapshot();
|
|
let backend_kind = snapshot.backend_kind().code().to_owned();
|
|
let network = snapshot.network().as_str().to_owned();
|
|
let closed = store.close().await;
|
|
return match closed {
|
|
std::result::Result::Ok(()) => {
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_STORE,
|
|
store_profile = self.profile_id.as_str(),
|
|
backend = backend_kind.as_str(),
|
|
network = network.as_str(),
|
|
"closed Store Desk Store runtime"
|
|
);
|
|
std::result::Result::Ok(())
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_SHUTDOWN_FAILED, "Store Desk Store shutdown failed").with_source(error),
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Resolves the composite Store target, opens Store through the facade and captures one initial readiness probe.
|
|
pub(crate) async fn initialize_store(management: &ksp_config_lib::ConfigManagement) -> StoreStartup {
|
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
|
let environment = match environment {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
|
|
};
|
|
let composite = crate::load_store_desk_composite(management);
|
|
let composite = match composite {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
|
|
};
|
|
let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
|
|
let profile = match profile {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return unavailable_startup(
|
|
std::option::Option::Some(composite.profile_id().to_owned()),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
error,
|
|
);
|
|
},
|
|
};
|
|
let resolved = management.engine().resolve_store_config_profile(&profile, &environment);
|
|
let resolved = match resolved {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return unavailable_startup(
|
|
std::option::Option::Some(profile.profile_id().to_owned()),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
error,
|
|
);
|
|
},
|
|
};
|
|
let profile_id = resolved.profile_id().to_owned();
|
|
let backend_kind = resolved.settings().backend_kind().code().to_owned();
|
|
let network = resolved.settings().network().as_str().to_owned();
|
|
let store = ksp_store_lib::Store::open(resolved.into_settings()).await;
|
|
let store = match store {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return unavailable_startup(
|
|
std::option::Option::Some(profile_id),
|
|
std::option::Option::Some(backend_kind),
|
|
std::option::Option::Some(network),
|
|
error,
|
|
);
|
|
},
|
|
};
|
|
let health = store.health().await;
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_STORE,
|
|
store_profile = profile_id.as_str(),
|
|
backend = backend_kind.as_str(),
|
|
network = network.as_str(),
|
|
health_state = store_health_state_code(health.state()),
|
|
pending_migration_count = health.pending_migration_count(),
|
|
"initialized Store Desk Store from composite-managed configuration"
|
|
);
|
|
return StoreStartup {
|
|
backend_kind: std::option::Option::Some(backend_kind),
|
|
diagnostic: non_ready_health_diagnostic(&health),
|
|
network: std::option::Option::Some(network),
|
|
profile_id: std::option::Option::Some(profile_id.clone()),
|
|
runtime: std::option::Option::Some(StoreRuntime { profile_id, store: tokio::sync::RwLock::new(std::option::Option::Some(store)) }),
|
|
};
|
|
}
|
|
|
|
fn non_ready_health_diagnostic(health: &ksp_store_lib::StoreHealthSnapshot) -> std::option::Option<crate::CommandErrorDto> {
|
|
if matches!(health.state(), ksp_store_lib::StoreHealthState::Ready) {
|
|
return std::option::Option::None;
|
|
}
|
|
let code = health.last_error_code();
|
|
return match code {
|
|
std::option::Option::Some(value) => std::option::Option::Some(crate::CommandErrorDto {
|
|
code: value.code().to_owned(),
|
|
domain: value.domain().to_owned(),
|
|
message: "Store health probe did not prove readiness".to_owned(),
|
|
}),
|
|
std::option::Option::None => std::option::Option::Some(crate::CommandErrorDto {
|
|
code: "store_not_ready".to_owned(),
|
|
domain: "store_desk".to_owned(),
|
|
message: "Store health probe did not prove readiness".to_owned(),
|
|
}),
|
|
};
|
|
}
|
|
|
|
fn store_health_state_code(state: ksp_store_lib::StoreHealthState) -> &'static str {
|
|
return match state {
|
|
ksp_store_lib::StoreHealthState::Ready => "ready",
|
|
ksp_store_lib::StoreHealthState::NotReady => "not_ready",
|
|
_ => "unknown",
|
|
};
|
|
}
|
|
|
|
fn unavailable_startup(
|
|
profile_id: std::option::Option<String>,
|
|
backend_kind: std::option::Option<String>,
|
|
network: std::option::Option<String>,
|
|
error: ksp_core_lib::Error,
|
|
) -> StoreStartup {
|
|
let diagnostic = crate::CommandErrorDto::from_error(&error);
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_STORE,
|
|
error_domain = diagnostic.domain.as_str(),
|
|
error_code = diagnostic.code.as_str(),
|
|
"Store Desk Store readiness is unavailable; keeping desktop shell available"
|
|
);
|
|
return StoreStartup {
|
|
backend_kind,
|
|
diagnostic: std::option::Option::Some(diagnostic),
|
|
network,
|
|
profile_id,
|
|
runtime: std::option::Option::None,
|
|
};
|
|
}
|