v0.3.7-pre.011
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/app_state.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Shared backend state owned by the Backfill Desk Tauri application.
|
||||
|
||||
@@ -123,7 +123,7 @@ impl crate::AppState {
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
config_document_count: document_count,
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
shell_phase: "pre.010-cancel-races".to_owned(),
|
||||
shell_phase: "pre.011-resume-checkpoint".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
@@ -219,6 +219,7 @@ impl crate::AppState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let retained_request = mapped.clone();
|
||||
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(mapped);
|
||||
let runtime = match runtime {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -226,7 +227,7 @@ impl crate::AppState {
|
||||
};
|
||||
let handle = runtime.handle();
|
||||
let snapshots = handle.snapshots();
|
||||
let installed = self.backfill_runs.install(job_id.clone(), handle);
|
||||
let installed = self.backfill_runs.install(job_id.clone(), handle, retained_request);
|
||||
if let std::result::Result::Err(error) = installed {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -264,6 +265,89 @@ impl crate::AppState {
|
||||
return std::result::Result::Ok(crate::BackfillRunLaunch { job_id, runtime, snapshots, store, transport });
|
||||
}
|
||||
|
||||
/// Prepares one in-session Resume from the retained Rust-only checkpoint using a new backend Job identity.
|
||||
pub(crate) fn prepare_backfill_resume(&self) -> ksp_core_lib::Result<crate::BackfillRunLaunch> {
|
||||
if self.shutdown_started.load(std::sync::atomic::Ordering::Acquire) {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
|
||||
"Backfill Desk cannot resume a Backfill run while application shutdown is in progress",
|
||||
));
|
||||
}
|
||||
let options = self.backfill_options();
|
||||
let options = match options {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !options.composition_ready {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
|
||||
"Backfill Desk cannot resume without ready Transport and Store composition",
|
||||
));
|
||||
}
|
||||
let job_id = self.backfill_runs.next_job_id();
|
||||
let job_id = match job_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mapped = self.backfill_runs.resume_request(job_id.clone());
|
||||
let mapped = match mapped {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let store_network_matches = options.store_network.as_deref() == std::option::Option::Some(mapped.network().as_str());
|
||||
let role_available = options.http_routes.iter().any(|route| return route.role == mapped.role().as_str());
|
||||
if !store_network_matches || !role_available {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
|
||||
"Backfill Desk retained Resume request no longer matches current Transport/Store composition",
|
||||
));
|
||||
}
|
||||
let retained_request = mapped.clone();
|
||||
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(mapped);
|
||||
let runtime = match runtime {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let handle = runtime.handle();
|
||||
let snapshots = handle.snapshots();
|
||||
let installed = self.backfill_runs.install(job_id.clone(), handle, retained_request);
|
||||
if let std::result::Result::Err(error) = installed {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transport = self.transport_runtime.as_ref();
|
||||
let transport = match transport {
|
||||
std::option::Option::Some(value) => value.pool(),
|
||||
std::option::Option::None => {
|
||||
let rollback = self.backfill_runs.rollback(&job_id);
|
||||
if let std::result::Result::Err(error) = rollback {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
|
||||
"Backfill Desk cannot resume a Backfill run without a ready HTTP Transport pool",
|
||||
));
|
||||
},
|
||||
};
|
||||
let store = self.store_startup.take_for_run();
|
||||
let store = match store {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let rollback = self.backfill_runs.rollback(&job_id);
|
||||
if let std::result::Result::Err(rollback_error) = rollback {
|
||||
return std::result::Result::Err(rollback_error);
|
||||
}
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_RUN,
|
||||
job_id = job_id.as_str(),
|
||||
"admitted Backfill Desk in-session Resume with a reissued Rust-only checkpoint"
|
||||
);
|
||||
return std::result::Result::Ok(crate::BackfillRunLaunch { job_id, runtime, snapshots, store, transport });
|
||||
}
|
||||
|
||||
/// Returns the current active or retained terminal Backfill status for frontend resynchronization.
|
||||
pub(crate) fn backfill_status(&self) -> ksp_core_lib::Result<std::option::Option<crate::BackfillRunStatusDto>> {
|
||||
let notification = self.backfill_runs.current_notification();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/backfill_run.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Single-active-run admission and launch ownership for Backfill Desk.
|
||||
|
||||
@@ -28,7 +28,7 @@ impl crate::BackfillRunLaunch {
|
||||
/// Single-run control slot retained by the application while one Backfill runtime is active.
|
||||
pub(crate) struct BackfillRunState {
|
||||
active: std::sync::Mutex<std::option::Option<ActiveBackfillRun>>,
|
||||
last_terminal: std::sync::Mutex<std::option::Option<ksp_job_api::JobNotification<ksp_job_backfill_lib::BackfillJobSnapshot>>>,
|
||||
last_terminal: std::sync::Mutex<std::option::Option<TerminalBackfillRun>>,
|
||||
next_sequence: std::sync::atomic::AtomicU64,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,12 @@ impl crate::BackfillRunState {
|
||||
}
|
||||
|
||||
/// Installs one control handle atomically and rejects concurrent Starts while another run owns the slot.
|
||||
pub(crate) fn install(&self, job_id: ksp_job_api::JobId, handle: ksp_job_backfill_lib::BackfillJobHandle) -> ksp_core_lib::Result<()> {
|
||||
pub(crate) fn install(
|
||||
&self,
|
||||
job_id: ksp_job_api::JobId,
|
||||
handle: ksp_job_backfill_lib::BackfillJobHandle,
|
||||
request: ksp_job_backfill_lib::BackfillRequest,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let active = self.active.lock();
|
||||
let mut active = match active {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -78,7 +83,7 @@ impl crate::BackfillRunState {
|
||||
"Backfill Desk already has an active Backfill run",
|
||||
));
|
||||
}
|
||||
*active = std::option::Option::Some(ActiveBackfillRun { handle, job_id });
|
||||
*active = std::option::Option::Some(ActiveBackfillRun { handle, job_id, request });
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
@@ -111,6 +116,7 @@ impl crate::BackfillRunState {
|
||||
},
|
||||
};
|
||||
let cancellation_requested = current.handle.is_cancellation_requested();
|
||||
let request = current.request.clone();
|
||||
let source = current.handle.snapshots();
|
||||
let notification = <ksp_job_backfill_lib::BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&source);
|
||||
if notification.state().is_terminal() {
|
||||
@@ -124,7 +130,7 @@ impl crate::BackfillRunState {
|
||||
));
|
||||
},
|
||||
};
|
||||
*terminal = std::option::Option::Some(notification);
|
||||
*terminal = std::option::Option::Some(TerminalBackfillRun { notification, request });
|
||||
}
|
||||
*active = std::option::Option::None;
|
||||
return std::result::Result::Ok(cancellation_requested);
|
||||
@@ -178,7 +184,8 @@ impl crate::BackfillRunState {
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::option::Option::Some(notification) = terminal.as_ref() {
|
||||
if let std::option::Option::Some(terminal) = terminal.as_ref() {
|
||||
let notification = &terminal.notification;
|
||||
if notification.id().as_str() != job_id {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RUN_MISMATCH,
|
||||
@@ -245,7 +252,58 @@ impl crate::BackfillRunState {
|
||||
));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(terminal.clone());
|
||||
return std::result::Result::Ok(terminal.as_ref().map(|terminal| return terminal.notification.clone()));
|
||||
}
|
||||
|
||||
/// Rebuilds the last terminal request with its opaque checkpoint reissued onto one new Job identity.
|
||||
pub(crate) fn resume_request(&self, job_id: ksp_job_api::JobId) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillRequest> {
|
||||
let active = self.active.lock();
|
||||
let active = match active {
|
||||
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,
|
||||
"Backfill Desk active-run resume state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
if active.is_some() {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RUN_ACTIVE,
|
||||
"Backfill Desk cannot resume while another Backfill run owns the single-run slot",
|
||||
));
|
||||
}
|
||||
drop(active);
|
||||
let terminal = self.last_terminal.lock();
|
||||
let terminal = match terminal {
|
||||
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,
|
||||
"Backfill Desk terminal resume state lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
let terminal = match terminal.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RESUME_UNAVAILABLE,
|
||||
"Backfill Desk has no retained terminal run available for in-session Resume",
|
||||
));
|
||||
},
|
||||
};
|
||||
let checkpoint = terminal.notification.snapshot().checkpoint();
|
||||
let checkpoint = match checkpoint {
|
||||
std::option::Option::Some(value) => value.clone(),
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_RESUME_UNAVAILABLE,
|
||||
"Backfill Desk retained terminal run has no safe checkpoint for in-session Resume",
|
||||
));
|
||||
},
|
||||
};
|
||||
return terminal.request.resume_for_job(job_id, checkpoint);
|
||||
}
|
||||
|
||||
/// Rolls back a just-installed admission when execution resources cannot be acquired before spawn.
|
||||
@@ -261,6 +319,13 @@ impl crate::BackfillRunState {
|
||||
struct ActiveBackfillRun {
|
||||
handle: ksp_job_backfill_lib::BackfillJobHandle,
|
||||
job_id: ksp_job_api::JobId,
|
||||
request: ksp_job_backfill_lib::BackfillRequest,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TerminalBackfillRun {
|
||||
notification: ksp_job_api::JobNotification<ksp_job_backfill_lib::BackfillJobSnapshot>,
|
||||
request: ksp_job_backfill_lib::BackfillRequest,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/dto_backfill.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Application-owned Backfill campaign DTOs and backend-derived request limits.
|
||||
|
||||
@@ -89,6 +89,19 @@ pub(crate) struct BackfillCancelResponseDto {
|
||||
pub(crate) state: String,
|
||||
}
|
||||
|
||||
/// Safe acknowledgement returned when one retained checkpoint is resumed into a new backend Job lifecycle.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillResumeResponseDto.ts")]
|
||||
pub(crate) struct BackfillResumeResponseDto {
|
||||
/// Whether the retained in-session checkpoint was accepted for a new run.
|
||||
pub(crate) accepted: bool,
|
||||
/// Newly allocated backend Job identifier owning the reissued checkpoint.
|
||||
pub(crate) job_id: String,
|
||||
/// Initial lifecycle state at the time Resume returns to the frontend.
|
||||
pub(crate) state: String,
|
||||
}
|
||||
|
||||
/// Safe projection proving that one app request mapped to the KSP Backfill contract.
|
||||
///
|
||||
/// Address and signature values are intentionally reduced to presence/count metadata. This DTO is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/errors.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Application-local error codes for Backfill Desk composition and desktop runtime surfaces.
|
||||
|
||||
@@ -12,6 +12,8 @@ pub(crate) const ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY: ksp_core_lib::ErrorC
|
||||
ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_composition_not_ready");
|
||||
/// Backfill Desk received a campaign field or logical route that cannot map to the Backfill contract.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_request_invalid");
|
||||
/// Backfill Desk has no retained safe checkpoint/request pair available for in-session Resume.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_RESUME_UNAVAILABLE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_resume_unavailable");
|
||||
/// Backfill Desk refuses a concurrent Start while another Backfill run owns the single-run slot.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_RUN_ACTIVE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_run_active");
|
||||
/// Backfill Desk received a stale or mismatched Job identity for an active-run control operation.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/lib.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
|
||||
|
||||
@@ -98,6 +98,8 @@ pub(crate) use self::dto_backfill::BackfillCancelResponseDto;
|
||||
pub(crate) use self::dto_backfill::BackfillRequestLimitsDto;
|
||||
/// Safe request preview produced after strict backend mapping.
|
||||
pub(crate) use self::dto_backfill::BackfillRequestPreviewDto;
|
||||
/// Safe acknowledgement returned after in-session checkpoint Resume admission.
|
||||
pub(crate) use self::dto_backfill::BackfillResumeResponseDto;
|
||||
/// App-owned campaign request received from the frontend.
|
||||
pub(crate) use self::dto_backfill::BackfillStartRequestDto;
|
||||
/// Safe immediate acknowledgement returned after one Backfill run is admitted and spawned.
|
||||
@@ -124,6 +126,8 @@ pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY;
|
||||
/// Backfill Desk received a campaign field that cannot map to the Backfill contract.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_REQUEST_INVALID;
|
||||
/// Backfill Desk has no retained safe checkpoint available for in-session Resume.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_RESUME_UNAVAILABLE;
|
||||
/// Backfill Desk refuses a concurrent Start while another Backfill run owns the single-run slot.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_RUN_ACTIVE;
|
||||
/// Backfill Desk rejects stale or mismatched Job identity control requests.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/tauri.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Tauri runtime assembly for the KSP Backfill desktop application.
|
||||
|
||||
@@ -79,6 +79,7 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
||||
return builder.invoke_handler(tauri::generate_handler![
|
||||
backfill_cancel,
|
||||
backfill_options,
|
||||
backfill_resume,
|
||||
backfill_start,
|
||||
backfill_status,
|
||||
backfill_validate_request,
|
||||
@@ -206,6 +207,32 @@ fn backfill_start(
|
||||
},
|
||||
};
|
||||
let response = launch.response();
|
||||
spawn_backfill_launch(app, launch);
|
||||
return std::result::Result::Ok(response);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn backfill_resume(
|
||||
app: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::BackfillResumeResponseDto, crate::CommandErrorDto> {
|
||||
let launch = state.prepare_backfill_resume();
|
||||
let launch = match launch {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(project_command_error("backfill_resume", crate::TRACING_DOMAIN_RUN, &error));
|
||||
},
|
||||
};
|
||||
let response = crate::BackfillResumeResponseDto {
|
||||
accepted: true,
|
||||
job_id: launch.job_id.as_str().to_owned(),
|
||||
state: ksp_job_api::JobState::Created.code().to_owned(),
|
||||
};
|
||||
spawn_backfill_launch(app, launch);
|
||||
return std::result::Result::Ok(response);
|
||||
}
|
||||
|
||||
fn spawn_backfill_launch(app: tauri::AppHandle, launch: crate::BackfillRunLaunch) {
|
||||
let monitor_app = app.clone();
|
||||
let monitor_source = launch.snapshots.clone();
|
||||
let _monitor_task = tauri::async_runtime::spawn(async move {
|
||||
@@ -224,7 +251,7 @@ fn backfill_start(
|
||||
);
|
||||
}
|
||||
});
|
||||
return std::result::Result::Ok(response);
|
||||
return;
|
||||
}
|
||||
|
||||
async fn monitor_backfill_status(app: tauri::AppHandle, source: ksp_job_backfill_lib::BackfillSnapshotSource) {
|
||||
|
||||
Reference in New Issue
Block a user