v0.3.6-pre.009-fix.001

This commit is contained in:
2026-09-01 16:17:22 +02:00
parent b861a1e3b8
commit 75b2d7e7f1
16 changed files with 366 additions and 233 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/runtime.rs
// version: 2
// version: 3
/// Stable Job kind code used by the concrete historical RAW transaction Backfill runtime.
pub const BACKFILL_JOB_KIND_CODE: &str = "solana.raw_transaction.backfill";
@@ -26,7 +26,7 @@ pub enum BackfillJobPhase {
Finished,
}
impl BackfillJobPhase {
impl crate::BackfillJobPhase {
/// Returns the stable safe code for this concrete runtime phase.
#[must_use]
pub const fn code(self) -> &'static str {
@@ -43,7 +43,7 @@ impl BackfillJobPhase {
/// Complete safe latest-value snapshot of one concrete historical RAW transaction Backfill Job.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackfillJobSnapshot {
phase: BackfillJobPhase,
phase: crate::BackfillJobPhase,
scope_kind: crate::BackfillScopeKind,
discovery_boundary: std::option::Option<crate::BackfillDiscoveryBoundary>,
candidates_selected: usize,
@@ -64,10 +64,10 @@ pub struct BackfillJobSnapshot {
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
}
impl BackfillJobSnapshot {
impl crate::BackfillJobSnapshot {
fn initial(request: &crate::BackfillRequest) -> Self {
return Self {
phase: BackfillJobPhase::Created,
phase: crate::BackfillJobPhase::Created,
scope_kind: request.scope().kind(),
discovery_boundary: std::option::Option::None,
candidates_selected: 0,
@@ -91,7 +91,7 @@ impl BackfillJobSnapshot {
/// Returns the concrete execution phase represented by this snapshot.
#[must_use]
pub const fn phase(&self) -> BackfillJobPhase {
pub const fn phase(&self) -> crate::BackfillJobPhase {
return self.phase;
}
@@ -207,11 +207,11 @@ impl BackfillJobSnapshot {
/// Cloneable runtime-neutral-facing latest-value source for concrete Backfill snapshots.
#[derive(Clone)]
pub struct BackfillSnapshotSource {
receiver: tokio::sync::watch::Receiver<ksp_job_api::JobNotification<BackfillJobSnapshot>>,
receiver: tokio::sync::watch::Receiver<ksp_job_api::JobNotification<crate::BackfillJobSnapshot>>,
}
impl ksp_job_api::JobSnapshotSource for BackfillSnapshotSource {
type Snapshot = BackfillJobSnapshot;
impl ksp_job_api::JobSnapshotSource for crate::BackfillSnapshotSource {
type Snapshot = crate::BackfillJobSnapshot;
fn current(&self) -> ksp_job_api::JobNotification<Self::Snapshot> {
return self.receiver.borrow().clone();
@@ -234,7 +234,7 @@ impl ksp_job_api::JobSnapshotSource for BackfillSnapshotSource {
}
}
impl std::fmt::Debug for BackfillSnapshotSource {
impl std::fmt::Debug for crate::BackfillSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let current = self.receiver.borrow();
return formatter.debug_struct("BackfillSnapshotSource").field("sequence", &current.sequence()).field("state", &current.state()).finish();
@@ -245,10 +245,10 @@ impl std::fmt::Debug for BackfillSnapshotSource {
#[derive(Clone)]
pub struct BackfillJobHandle {
control: BackfillRuntimeControl,
snapshots: BackfillSnapshotSource,
snapshots: crate::BackfillSnapshotSource,
}
impl BackfillJobHandle {
impl crate::BackfillJobHandle {
/// Requests cooperative cancellation and returns `true` only when accepted before terminal publication.
#[must_use]
pub fn cancel(&self) -> bool {
@@ -257,7 +257,7 @@ impl BackfillJobHandle {
/// Returns an independent latest-value snapshot source for one listener.
#[must_use]
pub fn snapshots(&self) -> BackfillSnapshotSource {
pub fn snapshots(&self) -> crate::BackfillSnapshotSource {
return self.snapshots.clone();
}
@@ -268,12 +268,12 @@ impl BackfillJobHandle {
}
}
impl std::fmt::Debug for BackfillJobHandle {
impl std::fmt::Debug for crate::BackfillJobHandle {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("BackfillJobHandle")
.field("cancellation_requested", &self.is_cancellation_requested())
.field("snapshot", &<BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&self.snapshots))
.field("snapshot", &<crate::BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&self.snapshots))
.finish();
}
}
@@ -282,19 +282,19 @@ impl std::fmt::Debug for BackfillJobHandle {
pub struct BackfillJobRuntime {
request: crate::BackfillRequest,
control: BackfillRuntimeControl,
cancellation: BackfillCancellationSignal,
publisher: BackfillRuntimePublisher,
handle: BackfillJobHandle,
cancellation: crate::BackfillCancellationSignal,
publisher: crate::BackfillRuntimePublisher,
handle: crate::BackfillJobHandle,
}
impl BackfillJobRuntime {
impl crate::BackfillJobRuntime {
/// Creates one runtime in `Created` state and its stable latest-value channel.
pub fn new(request: crate::BackfillRequest) -> ksp_core_lib::Result<Self> {
let kind = match ksp_job_api::JobKindCode::new(BACKFILL_JOB_KIND_CODE) {
let kind = match ksp_job_api::JobKindCode::new(crate::BACKFILL_JOB_KIND_CODE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let initial_snapshot = BackfillJobSnapshot::initial(&request);
let initial_snapshot = crate::BackfillJobSnapshot::initial(&request);
let initial = ksp_job_api::JobNotification::new(
request.job_id().clone(),
kind.clone(),
@@ -305,16 +305,16 @@ impl BackfillJobRuntime {
let (snapshot_sender, snapshot_receiver) = tokio::sync::watch::channel(initial);
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
let control = BackfillRuntimeControl::new(cancel_sender);
let cancellation = BackfillCancellationSignal::new(control.token(), cancel_receiver);
let snapshots = BackfillSnapshotSource { receiver: snapshot_receiver };
let handle = BackfillJobHandle { control: control.clone(), snapshots: snapshots.clone() };
let publisher = BackfillRuntimePublisher { sender: snapshot_sender, id: request.job_id().clone(), kind };
let cancellation = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver);
let snapshots = crate::BackfillSnapshotSource { receiver: snapshot_receiver };
let handle = crate::BackfillJobHandle { control: control.clone(), snapshots: snapshots.clone() };
let publisher = crate::BackfillRuntimePublisher { sender: snapshot_sender, id: request.job_id().clone(), kind };
return std::result::Result::Ok(Self { request, control, cancellation, publisher, handle });
}
/// Returns a cloneable control and latest-value observation handle before the runtime is moved into execution.
#[must_use]
pub fn handle(&self) -> BackfillJobHandle {
pub fn handle(&self) -> crate::BackfillJobHandle {
return self.handle.clone();
}
@@ -323,7 +323,7 @@ impl BackfillJobRuntime {
self,
transport: &ksp_onchain_transport_lib::HttpTransportPool,
store: &ksp_store_lib::Store,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
if self.cancellation.is_requested() {
let claimed = self.control.claim_normal_terminal();
if claimed != TerminalClaim::Cancelled {
@@ -331,7 +331,7 @@ impl BackfillJobRuntime {
}
return self.publisher.publish_cancelled_from_created();
}
let started = self.publisher.publish_running(BackfillJobPhase::Discovering);
let started = self.publisher.publish_running(crate::BackfillJobPhase::Discovering);
if let std::result::Result::Err(error) = started {
self.control.claim_failed();
return std::result::Result::Err(error);
@@ -393,7 +393,7 @@ impl BackfillJobRuntime {
};
}
fn finish_cancelled(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
fn finish_cancelled(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
let terminal = self.control.claim_normal_terminal();
if terminal != TerminalClaim::Cancelled {
return std::result::Result::Err(runtime_error("terminal.cancelled"));
@@ -408,7 +408,7 @@ impl BackfillJobRuntime {
/// Internal atomic terminal/cancellation arbitration shared by runtime and external handle.
#[derive(Clone)]
pub(crate) struct BackfillRuntimeControl {
struct BackfillRuntimeControl {
state: std::sync::Arc<std::sync::atomic::AtomicU8>,
token: ksp_job_api::JobCancellationToken,
cancel_sender: tokio::sync::watch::Sender<bool>,
@@ -416,7 +416,7 @@ pub(crate) struct BackfillRuntimeControl {
impl BackfillRuntimeControl {
/// Creates one active control state paired with the cancellation wake channel.
pub(crate) fn new(cancel_sender: tokio::sync::watch::Sender<bool>) -> Self {
fn new(cancel_sender: tokio::sync::watch::Sender<bool>) -> Self {
return Self {
state: std::sync::Arc::new(std::sync::atomic::AtomicU8::new(CONTROL_ACTIVE)),
token: ksp_job_api::JobCancellationToken::new(),
@@ -425,12 +425,12 @@ impl BackfillRuntimeControl {
}
/// Returns the runtime-neutral cancellation token mirrored by this control.
pub(crate) fn token(&self) -> ksp_job_api::JobCancellationToken {
fn token(&self) -> ksp_job_api::JobCancellationToken {
return self.token.clone();
}
/// Atomically accepts the first pre-terminal cancellation request.
pub(crate) fn request_cancellation(&self) -> bool {
fn request_cancellation(&self) -> bool {
let accepted = self
.state
.compare_exchange(CONTROL_ACTIVE, CONTROL_CANCELLATION_REQUESTED, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
@@ -442,12 +442,12 @@ impl BackfillRuntimeControl {
}
/// Returns whether cooperative cancellation was accepted.
pub(crate) fn is_cancellation_requested(&self) -> bool {
fn is_cancellation_requested(&self) -> bool {
return self.token.is_cancellation_requested();
}
/// Atomically resolves the completion-versus-cancellation terminal race.
pub(crate) fn claim_normal_terminal(&self) -> TerminalClaim {
fn claim_normal_terminal(&self) -> TerminalClaim {
let completed =
self.state.compare_exchange(CONTROL_ACTIVE, CONTROL_COMPLETED, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire);
if completed.is_ok() {
@@ -466,7 +466,7 @@ impl BackfillRuntimeControl {
}
/// Marks a non-terminal control as failed, overriding a pending cancellation request.
pub(crate) fn claim_failed(&self) {
fn claim_failed(&self) {
loop {
let state = self.state.load(std::sync::atomic::Ordering::Acquire);
if matches!(state, CONTROL_COMPLETED | CONTROL_CANCELLED | CONTROL_FAILED) {
@@ -487,7 +487,7 @@ pub(crate) struct BackfillCancellationSignal {
receiver: tokio::sync::watch::Receiver<bool>,
}
impl BackfillCancellationSignal {
impl crate::BackfillCancellationSignal {
/// Creates one signal from the runtime-neutral token and Tokio wake receiver.
pub(crate) fn new(token: ksp_job_api::JobCancellationToken, receiver: tokio::sync::watch::Receiver<bool>) -> Self {
return Self { token, receiver };
@@ -529,7 +529,7 @@ async fn wait_for_cancellation(token: &ksp_job_api::JobCancellationToken, receiv
/// Internal result of atomically claiming a normal terminal state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TerminalClaim {
enum TerminalClaim {
Completed,
Cancelled,
Failed,
@@ -538,23 +538,23 @@ pub(crate) enum TerminalClaim {
/// Internal latest-value publisher owning the concrete Backfill notification stream.
#[derive(Clone)]
pub(crate) struct BackfillRuntimePublisher {
sender: tokio::sync::watch::Sender<ksp_job_api::JobNotification<BackfillJobSnapshot>>,
sender: tokio::sync::watch::Sender<ksp_job_api::JobNotification<crate::BackfillJobSnapshot>>,
id: ksp_job_api::JobId,
kind: ksp_job_api::JobKindCode,
}
impl BackfillRuntimePublisher {
impl crate::BackfillRuntimePublisher {
/// Publishes one non-terminal running phase.
pub(crate) fn publish_running(&self, phase: BackfillJobPhase) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_running(&self, phase: crate::BackfillJobPhase) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Running, |snapshot| {
snapshot.phase = phase;
});
}
/// Publishes the complete bounded discovery result as the current execution snapshot.
pub(crate) fn publish_discovery(&self, discovery: &crate::BackfillDiscovery) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_discovery(&self, discovery: &crate::BackfillDiscovery) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Running, |snapshot| {
snapshot.phase = BackfillJobPhase::Executing;
snapshot.phase = crate::BackfillJobPhase::Executing;
snapshot.discovery_boundary = std::option::Option::Some(discovery.boundary());
snapshot.candidates_selected = discovery.candidates().len();
});
@@ -566,25 +566,25 @@ impl BackfillRuntimePublisher {
progress: &crate::BackfillExecutionProgress,
cancelling: bool,
draining: bool,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
let state = if cancelling { ksp_job_api::JobState::Cancelling } else { ksp_job_api::JobState::Running };
return self.publish_with(state, |snapshot| {
snapshot.phase = if draining { BackfillJobPhase::Draining } else { BackfillJobPhase::Executing };
snapshot.phase = if draining { crate::BackfillJobPhase::Draining } else { crate::BackfillJobPhase::Executing };
apply_progress(snapshot, progress);
});
}
/// Publishes cancellation observation before terminal cancellation.
pub(crate) fn publish_cancelling(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_cancelling(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelling, |snapshot| {
snapshot.phase = BackfillJobPhase::Draining;
snapshot.phase = crate::BackfillJobPhase::Draining;
});
}
/// Publishes the drained batch state while cancellation is terminalizing.
pub(crate) fn publish_batch_cancelling(&self, batch: &crate::BackfillExecutionBatch) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_batch_cancelling(&self, batch: &crate::BackfillExecutionBatch) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelling, |snapshot| {
snapshot.phase = BackfillJobPhase::Draining;
snapshot.phase = crate::BackfillJobPhase::Draining;
apply_batch(snapshot, batch);
});
}
@@ -595,16 +595,16 @@ impl BackfillRuntimePublisher {
batch: &crate::BackfillExecutionBatch,
state: ksp_job_api::JobState,
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(state, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
snapshot.failure_code = failure_code;
apply_batch(snapshot, batch);
});
}
/// Publishes a terminal failure before a batch exists.
pub(crate) fn publish_failed(&self, code: ksp_core_lib::ErrorCode) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_failed(&self, code: ksp_core_lib::ErrorCode) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_terminal(ksp_job_api::JobState::Failed, std::option::Option::Some(code));
}
@@ -613,23 +613,23 @@ impl BackfillRuntimePublisher {
&self,
state: ksp_job_api::JobState,
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(state, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
snapshot.failure_code = failure_code;
});
}
/// Publishes direct Created-to-Cancelled termination before execution starts.
pub(crate) fn publish_cancelled_from_created(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_cancelled_from_created(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelled, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
});
}
fn publish_with<F>(&self, state: ksp_job_api::JobState, update: F) -> ksp_core_lib::Result<BackfillJobSnapshot>
fn publish_with<F>(&self, state: ksp_job_api::JobState, update: F) -> ksp_core_lib::Result<crate::BackfillJobSnapshot>
where
F: FnOnce(&mut BackfillJobSnapshot),
F: FnOnce(&mut crate::BackfillJobSnapshot),
{
let current = self.sender.borrow().clone();
if current.state().is_terminal() {
@@ -668,7 +668,7 @@ fn valid_snapshot_transition(source: ksp_job_api::JobState, target: ksp_job_api:
);
}
fn apply_progress(snapshot: &mut BackfillJobSnapshot, progress: &crate::BackfillExecutionProgress) {
fn apply_progress(snapshot: &mut crate::BackfillJobSnapshot, progress: &crate::BackfillExecutionProgress) {
snapshot.candidates_admitted = progress.admitted_count();
snapshot.candidates_finished = progress.finished_count();
snapshot.entities_inserted = progress.inserted_count();
@@ -685,7 +685,7 @@ fn apply_progress(snapshot: &mut BackfillJobSnapshot, progress: &crate::Backfill
snapshot.checkpoint = std::option::Option::Some(progress.checkpoint().clone());
}
fn apply_batch(snapshot: &mut BackfillJobSnapshot, batch: &crate::BackfillExecutionBatch) {
fn apply_batch(snapshot: &mut crate::BackfillJobSnapshot, batch: &crate::BackfillExecutionBatch) {
snapshot.candidates_admitted = batch.admitted_count();
snapshot.candidates_finished = batch.finished_count();
snapshot.entities_inserted = batch.inserted_count();