0.3.15-pre.012
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
|
||||
|
||||
@@ -117,23 +117,26 @@ impl crate::AppState {
|
||||
|
||||
/// Starts one independent route Worker after repeating complete Start-time revalidation, sharing Store only on an exact network match.
|
||||
pub(crate) async fn start_route(&self, request: &crate::RawIngestRouteStartRequestDto) -> ksp_core_lib::Result<crate::RouteRuntimeLaunch> {
|
||||
let generation = {
|
||||
let generation = self.inventory_generation.lock();
|
||||
match generation {
|
||||
std::result::Result::Ok(value) => *value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_LOCK_FAILED,
|
||||
"Raw Transaction Ingest Desk route inventory generation lock is poisoned",
|
||||
));
|
||||
},
|
||||
}
|
||||
let admission = self.route_runtime.ensure_start_admission_open();
|
||||
if let std::result::Result::Err(error) = admission {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let generation = self.inventory_generation.lock();
|
||||
let generation = match generation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_LOCK_FAILED,
|
||||
"Raw Transaction Ingest Desk route inventory generation lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
let prepared = crate::prepare_route_start(&self.config_management, generation, request);
|
||||
let prepared = crate::prepare_route_start(&self.config_management, *generation, request);
|
||||
let prepared = match prepared {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
std::mem::drop(generation);
|
||||
return crate::start_route_runtime(std::sync::Arc::clone(&self.route_runtime), prepared).await;
|
||||
}
|
||||
|
||||
@@ -147,6 +150,16 @@ impl crate::AppState {
|
||||
return self.route_runtime.stop_and_wait(request).await;
|
||||
}
|
||||
|
||||
/// Closes route Start admission once and requests cooperative Stop for all routes during application shutdown.
|
||||
pub(crate) fn begin_shutdown(&self) -> ksp_core_lib::Result<bool> {
|
||||
return self.route_runtime.begin_shutdown();
|
||||
}
|
||||
|
||||
/// Waits for bounded cleanup of all route Workers and the shared Store during application shutdown.
|
||||
pub(crate) async fn shutdown_routes(&self) -> ksp_core_lib::Result<()> {
|
||||
return self.route_runtime.shutdown_and_wait().await;
|
||||
}
|
||||
|
||||
/// Builds the safe scaffold status exposed by the shell.
|
||||
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
|
||||
|
||||
@@ -99,7 +99,7 @@ impl crate::RawIngestCommitment {
|
||||
|
||||
/// Safe request used to revalidate and reconstruct one future route Start without accepting physical resources from the frontend.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartRequestDto.ts")]
|
||||
pub(crate) struct RawIngestRouteStartRequestDto {
|
||||
/// Confirmed/finalized commitment selected for the future Worker.
|
||||
@@ -114,7 +114,7 @@ pub(crate) struct RawIngestRouteStartRequestDto {
|
||||
|
||||
/// Safe targeted Stop request containing only the logical Worker route identity.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStopRequestDto.ts")]
|
||||
pub(crate) struct RawIngestRouteStopRequestDto {
|
||||
/// Safe logical network-profile identifier that owns the Worker route.
|
||||
@@ -123,6 +123,36 @@ pub(crate) struct RawIngestRouteStopRequestDto {
|
||||
pub(crate) route_id: crate::RawIngestRouteId,
|
||||
}
|
||||
|
||||
const RAW_INGEST_PROFILE_ID_MAX_BYTES: usize = 256;
|
||||
|
||||
impl crate::RawIngestRouteStartRequestDto {
|
||||
/// Validates the only caller-controlled free-form logical identifier before Config or runtime reconstruction.
|
||||
pub(crate) fn validate_logical_identity(&self) -> ksp_core_lib::Result<()> {
|
||||
return validate_profile_id(self.profile_id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::RawIngestRouteStopRequestDto {
|
||||
/// Validates the only caller-controlled free-form logical identifier before runtime lookup.
|
||||
pub(crate) fn validate_logical_identity(&self) -> ksp_core_lib::Result<()> {
|
||||
return validate_profile_id(self.profile_id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_profile_id(profile_id: &str) -> ksp_core_lib::Result<()> {
|
||||
if profile_id.is_empty()
|
||||
|| profile_id.len() > RAW_INGEST_PROFILE_ID_MAX_BYTES
|
||||
|| profile_id.trim() != profile_id
|
||||
|| profile_id.chars().any(char::is_control)
|
||||
{
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_REQUEST_INVALID,
|
||||
"Raw Transaction Ingest Desk route request contains an invalid logical profile identifier",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Safe acknowledgement that exact Start-time Transport resources were reconstructed and validated without Worker launch.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Stable Raw Transaction Ingest Desk application error codes.
|
||||
|
||||
@@ -17,6 +17,9 @@ pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "frontend_log_target_invalid");
|
||||
/// Frontend logging payload exceeds the bounded technical IPC contract.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "frontend_log_payload_invalid");
|
||||
/// Raw Transaction Ingest Desk could not install managed Logging or its safe fallback.
|
||||
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "logging_bootstrap_failed");
|
||||
@@ -46,6 +49,15 @@ pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED: ksp_core_lib::E
|
||||
/// The multi-route runtime reservation/handle state changed unexpectedly.
|
||||
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_state_invalid");
|
||||
/// Application shutdown has closed admission for new route Worker starts.
|
||||
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_shutting_down");
|
||||
/// Application shutdown could not join all route Workers and shared Store cleanup within the bounded deadline.
|
||||
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_shutdown_failed");
|
||||
/// A Start/Stop IPC request contains an invalid logical identifier or unsupported shape.
|
||||
pub(crate) const ERROR_CODE_ROUTE_REQUEST_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_request_invalid");
|
||||
/// Start request references an inventory generation that is no longer current.
|
||||
pub(crate) const ERROR_CODE_ROUTE_START_STALE_INVENTORY: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "stale_inventory");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/frontend_logging.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! KSP-owned bridge for technical log events emitted by Raw Transaction Ingest Desk frontend scripts.
|
||||
|
||||
@@ -7,7 +7,7 @@ use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Log payload sent by Raw Transaction Ingest Desk frontend scripts.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/frontend_logging/FrontendLogPayloadDto.ts")]
|
||||
pub(crate) struct FrontendLogPayloadDto {
|
||||
/// Lowercase KSP log level.
|
||||
@@ -34,8 +34,15 @@ enum FrontendLogTarget {
|
||||
Splash,
|
||||
}
|
||||
|
||||
const FRONTEND_LOG_MESSAGE_MAX_BYTES: usize = 8192;
|
||||
const FRONTEND_LOG_SELECTOR_MAX_BYTES: usize = 16;
|
||||
|
||||
/// Emits one validated frontend event through the KSP Logging facade.
|
||||
pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
|
||||
let shape = validate_payload_shape(&payload);
|
||||
if let std::result::Result::Err(error) = shape {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let level = parse_level(payload.level.as_str());
|
||||
let level = match level {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -50,26 +57,55 @@ pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_cor
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_payload_shape(payload: &FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
|
||||
if payload.level.len() > FRONTEND_LOG_SELECTOR_MAX_BYTES
|
||||
|| payload.target_id.len() > FRONTEND_LOG_SELECTOR_MAX_BYTES
|
||||
|| payload.message.len() > FRONTEND_LOG_MESSAGE_MAX_BYTES
|
||||
|| payload.message.chars().any(char::is_control)
|
||||
{
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID,
|
||||
"Frontend log payload exceeds the bounded technical IPC contract",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn parse_level(level: &str) -> ksp_core_lib::Result<FrontendLogLevel> {
|
||||
return match level.trim().to_ascii_lowercase().as_str() {
|
||||
"trace" => std::result::Result::Ok(FrontendLogLevel::Trace),
|
||||
"debug" => std::result::Result::Ok(FrontendLogLevel::Debug),
|
||||
"info" => std::result::Result::Ok(FrontendLogLevel::Info),
|
||||
"warn" => std::result::Result::Ok(FrontendLogLevel::Warn),
|
||||
"error" => std::result::Result::Ok(FrontendLogLevel::Error),
|
||||
_ => std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported")),
|
||||
};
|
||||
let level = level.trim();
|
||||
if level.eq_ignore_ascii_case("trace") {
|
||||
return std::result::Result::Ok(FrontendLogLevel::Trace);
|
||||
}
|
||||
if level.eq_ignore_ascii_case("debug") {
|
||||
return std::result::Result::Ok(FrontendLogLevel::Debug);
|
||||
}
|
||||
if level.eq_ignore_ascii_case("info") {
|
||||
return std::result::Result::Ok(FrontendLogLevel::Info);
|
||||
}
|
||||
if level.eq_ignore_ascii_case("warn") {
|
||||
return std::result::Result::Ok(FrontendLogLevel::Warn);
|
||||
}
|
||||
if level.eq_ignore_ascii_case("error") {
|
||||
return std::result::Result::Ok(FrontendLogLevel::Error);
|
||||
}
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported"));
|
||||
}
|
||||
|
||||
fn parse_target(target_id: &str) -> ksp_core_lib::Result<FrontendLogTarget> {
|
||||
return match target_id.trim().to_ascii_lowercase().as_str() {
|
||||
"frontend" => std::result::Result::Ok(FrontendLogTarget::Frontend),
|
||||
"main" => std::result::Result::Ok(FrontendLogTarget::Main),
|
||||
"splash" => std::result::Result::Ok(FrontendLogTarget::Splash),
|
||||
_ => {
|
||||
std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID, "Frontend log target identifier is not supported"))
|
||||
},
|
||||
};
|
||||
let target_id = target_id.trim();
|
||||
if target_id.eq_ignore_ascii_case("frontend") {
|
||||
return std::result::Result::Ok(FrontendLogTarget::Frontend);
|
||||
}
|
||||
if target_id.eq_ignore_ascii_case("main") {
|
||||
return std::result::Result::Ok(FrontendLogTarget::Main);
|
||||
}
|
||||
if target_id.eq_ignore_ascii_case("splash") {
|
||||
return std::result::Result::Ok(FrontendLogTarget::Splash);
|
||||
}
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID,
|
||||
"Frontend log target identifier is not supported",
|
||||
));
|
||||
}
|
||||
|
||||
fn emit_validated_frontend_log(level: FrontendLogLevel, target: FrontendLogTarget, message: &str) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
|
||||
|
||||
@@ -113,18 +113,26 @@ pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||
/// Frontend logging payload exceeds the bounded technical IPC contract.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID;
|
||||
/// Frontend logging requested an unsupported target.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||
/// Managed and fallback Logging initialization failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||
/// Config-only route inventory cannot be projected safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_INVENTORY_INVALID;
|
||||
/// Start/Stop IPC request contains an invalid logical shape.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_REQUEST_INVALID;
|
||||
/// A Worker runtime already owns the same logical route in the shared application runtime.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_ACTIVE;
|
||||
/// Selected route cannot share the currently open Store because its network differs.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_NETWORK_MISMATCH;
|
||||
/// No selected route Worker runtime is currently active.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE;
|
||||
/// Application shutdown could not complete all bounded route/Store cleanup.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED;
|
||||
/// Application shutdown has closed admission for new route Worker starts.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN;
|
||||
/// Store or Worker startup failed after Start-time route revalidation.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_START_FAILED;
|
||||
/// The multi-route runtime reservation or handle state changed unexpectedly.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Multi-route Store and independent Worker lifecycle owned by Raw Transaction Ingest Desk.
|
||||
|
||||
const ROUTE_STOP_CLEANUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
|
||||
const ROUTE_SHUTDOWN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
const ROUTE_STOP_CLEANUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
const STORE_RECLAIM_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
|
||||
const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
|
||||
@@ -13,6 +14,7 @@ struct RouteRuntimeInner {
|
||||
network: std::option::Option<String>,
|
||||
next_sequence: u64,
|
||||
routes: std::vec::Vec<RouteRuntimeSlot>,
|
||||
shutting_down: bool,
|
||||
store: std::option::Option<std::sync::Arc<ksp_store_lib::Store>>,
|
||||
store_closing_token: std::option::Option<u64>,
|
||||
store_opening_token: std::option::Option<u64>,
|
||||
@@ -34,6 +36,7 @@ impl crate::RouteRuntimeState {
|
||||
network: std::option::Option::None,
|
||||
next_sequence: 0,
|
||||
routes: std::vec::Vec::new(),
|
||||
shutting_down: false,
|
||||
store: std::option::Option::None,
|
||||
store_closing_token: std::option::Option::None,
|
||||
store_opening_token: std::option::Option::None,
|
||||
@@ -41,12 +44,34 @@ impl crate::RouteRuntimeState {
|
||||
};
|
||||
}
|
||||
|
||||
/// Rejects new route Start work after application shutdown has closed admission.
|
||||
pub(crate) fn ensure_start_admission_open(&self) -> ksp_core_lib::Result<()> {
|
||||
let inner = self.inner.lock();
|
||||
let inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
if inner.shutting_down {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN,
|
||||
"Raw Transaction Ingest Desk cannot start a route while application shutdown is in progress",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn reserve(&self, prepared: &crate::PreparedRouteStart) -> ksp_core_lib::Result<RouteRuntimeReservation> {
|
||||
let inner = self.inner.lock();
|
||||
let mut inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
if inner.shutting_down {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN,
|
||||
"Raw Transaction Ingest Desk cannot start a route while application shutdown is in progress",
|
||||
));
|
||||
}
|
||||
if inner.store_closing_token.is_some() {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_ACTIVE,
|
||||
@@ -111,7 +136,7 @@ impl crate::RouteRuntimeState {
|
||||
if needs_store_open {
|
||||
inner.store_opening_token = std::option::Option::Some(sequence);
|
||||
}
|
||||
inner.routes.push(RouteRuntimeSlot::Starting { identity: identity.clone(), token: sequence });
|
||||
inner.routes.push(RouteRuntimeSlot::Starting { identity: identity.clone(), stop_requested: false, token: sequence });
|
||||
return std::result::Result::Ok(RouteRuntimeReservation { identity, needs_store_open, token: sequence, worker_id });
|
||||
}
|
||||
|
||||
@@ -173,7 +198,19 @@ impl crate::RouteRuntimeState {
|
||||
));
|
||||
},
|
||||
};
|
||||
let state = project_worker_state(handle.snapshot_source().current().worker_snapshot().state());
|
||||
let stop_requested = match &inner.routes[position] {
|
||||
RouteRuntimeSlot::Starting { stop_requested, .. } => *stop_requested,
|
||||
RouteRuntimeSlot::Active { .. } => false,
|
||||
};
|
||||
let should_stop = stop_requested || inner.shutting_down;
|
||||
if should_stop {
|
||||
let _accepted = handle.request_stop();
|
||||
}
|
||||
let state = if should_stop {
|
||||
crate::RawIngestRouteState::Stopping
|
||||
} else {
|
||||
project_worker_state(handle.snapshot_source().current().worker_snapshot().state())
|
||||
};
|
||||
inner.routes[position] = RouteRuntimeSlot::Active { handle, identity: reservation.identity.clone(), token: reservation.token };
|
||||
return std::result::Result::Ok(reservation.identity.dto(state));
|
||||
}
|
||||
@@ -184,7 +221,12 @@ impl crate::RouteRuntimeState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
let cancelled_terminal = inner.routes.iter().find(|slot| return slot.token() == token).and_then(RouteRuntimeSlot::cancelled_start_terminal);
|
||||
inner.routes.retain(|slot| return slot.token() != token);
|
||||
if let std::option::Option::Some(terminal) = cancelled_terminal {
|
||||
inner.last_terminals.retain(|previous| return previous.profile_id != terminal.profile_id || previous.route_id != terminal.route_id);
|
||||
inner.last_terminals.push(terminal);
|
||||
}
|
||||
if inner.store_opening_token == std::option::Option::Some(token) {
|
||||
inner.store_opening_token = std::option::Option::None;
|
||||
}
|
||||
@@ -272,8 +314,71 @@ impl crate::RouteRuntimeState {
|
||||
return std::result::Result::Ok(values);
|
||||
}
|
||||
|
||||
/// Closes route Start admission exactly once and requests cooperative Stop for all active or still-starting routes.
|
||||
pub(crate) fn begin_shutdown(&self) -> ksp_core_lib::Result<bool> {
|
||||
let inner = self.inner.lock();
|
||||
let mut inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
if inner.shutting_down {
|
||||
return std::result::Result::Ok(false);
|
||||
}
|
||||
inner.shutting_down = true;
|
||||
for slot in &mut inner.routes {
|
||||
match slot {
|
||||
RouteRuntimeSlot::Starting { stop_requested, .. } => *stop_requested = true,
|
||||
RouteRuntimeSlot::Active { handle, .. } => {
|
||||
let _accepted = handle.request_stop();
|
||||
},
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(true);
|
||||
}
|
||||
|
||||
/// Waits until all owned route Workers and the shared Store have completed bounded application-shutdown cleanup.
|
||||
pub(crate) async fn shutdown_and_wait(&self) -> ksp_core_lib::Result<()> {
|
||||
let begin = self.begin_shutdown();
|
||||
if let std::result::Result::Err(error) = begin {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let started = std::time::Instant::now();
|
||||
loop {
|
||||
let complete = self.shutdown_complete();
|
||||
let complete = match complete {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if complete {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
if started.elapsed() >= ROUTE_SHUTDOWN_TIMEOUT {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED,
|
||||
"Raw Transaction Ingest Desk application shutdown did not release all route Workers and shared Store state before the bounded deadline",
|
||||
));
|
||||
}
|
||||
tokio::time::sleep(STORE_RECLAIM_POLL_INTERVAL).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown_complete(&self) -> ksp_core_lib::Result<bool> {
|
||||
let inner = self.inner.lock();
|
||||
let inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
return std::result::Result::Ok(
|
||||
inner.routes.is_empty() && inner.store.is_none() && inner.store_opening_token.is_none() && inner.store_closing_token.is_none(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Requests cooperative Stop for one exact logical route and waits for its Worker cleanup.
|
||||
pub(crate) async fn stop_and_wait(&self, request: &crate::RawIngestRouteStopRequestDto) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
|
||||
let identity = request.validate_logical_identity();
|
||||
if let std::result::Result::Err(error) = identity {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let target = self.stop_target(request);
|
||||
let target = match target {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -281,6 +386,7 @@ impl crate::RouteRuntimeState {
|
||||
};
|
||||
let (handle, identity, token) = match target {
|
||||
RouteStopTarget::Active { handle, identity, token } => (handle, identity, token),
|
||||
RouteStopTarget::Starting { identity, token } => return self.wait_route_cleanup(token, &identity).await,
|
||||
RouteStopTarget::Terminal(value) => return std::result::Result::Ok(value),
|
||||
RouteStopTarget::TerminalClosing => return self.wait_terminal_store_cleanup(request).await,
|
||||
};
|
||||
@@ -296,33 +402,30 @@ impl crate::RouteRuntimeState {
|
||||
|
||||
fn stop_target(&self, request: &crate::RawIngestRouteStopRequestDto) -> ksp_core_lib::Result<RouteStopTarget> {
|
||||
let inner = self.inner.lock();
|
||||
let inner = match inner {
|
||||
let mut inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
let slot = inner.routes.iter().find(|slot| return slot.matches_identity(request.profile_id.as_str(), request.route_id));
|
||||
return match slot {
|
||||
std::option::Option::Some(RouteRuntimeSlot::Active { handle, identity, token }) => {
|
||||
std::result::Result::Ok(RouteStopTarget::Active { handle: handle.clone(), identity: identity.clone(), token: *token })
|
||||
},
|
||||
std::option::Option::Some(RouteRuntimeSlot::Starting { .. }) => std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
let position = inner.routes.iter().position(|slot| return slot.matches_identity(request.profile_id.as_str(), request.route_id));
|
||||
if let std::option::Option::Some(position) = position {
|
||||
return match &mut inner.routes[position] {
|
||||
RouteRuntimeSlot::Active { handle, identity, token } => {
|
||||
std::result::Result::Ok(RouteStopTarget::Active { handle: handle.clone(), identity: identity.clone(), token: *token })
|
||||
},
|
||||
RouteRuntimeSlot::Starting { identity, stop_requested, token } => {
|
||||
*stop_requested = true;
|
||||
std::result::Result::Ok(RouteStopTarget::Starting { identity: identity.clone(), token: *token })
|
||||
},
|
||||
};
|
||||
}
|
||||
let terminal = inner.last_terminals.iter().find(|terminal| return terminal.profile_id == request.profile_id && terminal.route_id == request.route_id);
|
||||
return match terminal {
|
||||
std::option::Option::Some(value) if inner.store_closing_token.is_none() => std::result::Result::Ok(RouteStopTarget::Terminal(value.clone())),
|
||||
std::option::Option::Some(_) => std::result::Result::Ok(RouteStopTarget::TerminalClosing),
|
||||
std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE,
|
||||
"Raw Transaction Ingest Desk route Worker is still starting and cannot be stopped yet",
|
||||
"Raw Transaction Ingest Desk has no active Worker for the selected logical route",
|
||||
)),
|
||||
std::option::Option::None => {
|
||||
let terminal =
|
||||
inner.last_terminals.iter().find(|terminal| return terminal.profile_id == request.profile_id && terminal.route_id == request.route_id);
|
||||
match terminal {
|
||||
std::option::Option::Some(value) if inner.store_closing_token.is_none() => {
|
||||
std::result::Result::Ok(RouteStopTarget::Terminal(value.clone()))
|
||||
},
|
||||
std::option::Option::Some(_) => std::result::Result::Ok(RouteStopTarget::TerminalClosing),
|
||||
std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE,
|
||||
"Raw Transaction Ingest Desk has no active Worker for the selected logical route",
|
||||
)),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -668,6 +771,10 @@ enum RouteStopTarget {
|
||||
identity: RouteRuntimeIdentity,
|
||||
token: u64,
|
||||
},
|
||||
Starting {
|
||||
identity: RouteRuntimeIdentity,
|
||||
token: u64,
|
||||
},
|
||||
Terminal(crate::RawIngestRouteRuntimeDto),
|
||||
TerminalClosing,
|
||||
}
|
||||
@@ -682,6 +789,7 @@ struct RouteRuntimeReservation {
|
||||
enum RouteRuntimeSlot {
|
||||
Starting {
|
||||
identity: RouteRuntimeIdentity,
|
||||
stop_requested: bool,
|
||||
token: u64,
|
||||
},
|
||||
Active {
|
||||
@@ -706,6 +814,13 @@ impl RouteRuntimeSlot {
|
||||
};
|
||||
}
|
||||
|
||||
fn cancelled_start_terminal(&self) -> std::option::Option<crate::RawIngestRouteRuntimeDto> {
|
||||
return match self {
|
||||
Self::Starting { identity, stop_requested: true, .. } => std::option::Option::Some(identity.dto(crate::RawIngestRouteState::Stopped)),
|
||||
Self::Starting { stop_requested: false, .. } | Self::Active { .. } => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
fn token(&self) -> u64 {
|
||||
return match self {
|
||||
Self::Starting { token, .. } | Self::Active { token, .. } => *token,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Start-time Config revalidation and exact Transport/Store resource reconstruction for route validation and runtime launch.
|
||||
|
||||
@@ -27,6 +27,10 @@ pub(crate) fn prepare_route_start(
|
||||
current_generation: u32,
|
||||
request: &crate::RawIngestRouteStartRequestDto,
|
||||
) -> ksp_core_lib::Result<crate::PreparedRouteStart> {
|
||||
let identity = request.validate_logical_identity();
|
||||
if let std::result::Result::Err(error) = identity {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if current_generation == 0 || request.inventory_generation != current_generation {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
|
||||
|
||||
@@ -23,6 +23,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
||||
builder = builder.manage(app_state);
|
||||
builder = builder.plugin(tauri_plugin_tracing::Builder::new().build::<tauri::Wry>());
|
||||
builder = configure_commands(builder);
|
||||
builder = configure_window_events(builder);
|
||||
builder = builder.setup(|app| {
|
||||
let splash = crate::require_splash_window(app);
|
||||
if let std::result::Result::Err(error) = splash {
|
||||
@@ -90,6 +91,66 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
||||
]);
|
||||
}
|
||||
|
||||
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
return builder.on_window_event(|window, event| {
|
||||
if window.label() != "main" {
|
||||
return;
|
||||
}
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let app_handle = window.app_handle().clone();
|
||||
let state = app_handle.state::<crate::AppState>();
|
||||
let begin = state.begin_shutdown();
|
||||
let begin = match begin {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
error_domain = error.code().domain(),
|
||||
error_code = error.code().code(),
|
||||
"Raw Transaction Ingest Desk could not enter graceful shutdown; exiting after safe error projection"
|
||||
);
|
||||
app_handle.exit(1);
|
||||
return;
|
||||
},
|
||||
};
|
||||
if !begin {
|
||||
return;
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
"Raw Transaction Ingest Desk main-window close requested; route Start admission closed and cooperative shutdown requested"
|
||||
);
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = app_handle.state::<crate::AppState>();
|
||||
let shutdown = state.shutdown_routes().await;
|
||||
match shutdown {
|
||||
std::result::Result::Ok(()) => {
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
"Raw Transaction Ingest Desk route and shared Store shutdown completed; exiting application"
|
||||
);
|
||||
app_handle.exit(0);
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||
error_domain = error.code().domain(),
|
||||
error_code = error.code().code(),
|
||||
"Raw Transaction Ingest Desk bounded route shutdown failed; exiting application after bounded cleanup attempt"
|
||||
);
|
||||
app_handle.exit(1);
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
|
||||
Reference in New Issue
Block a user