SQLite engine: queues — Queue/JobHandle over the writer slot, engine-side backoff curve, extent guard (task sqlite-engine-queues)
queue.rs: SqliteQueueHandle (QueueOpts-carrying, stamp-resolving enqueue over the seam's shared resolution arithmetic), claim_one/ claim_batch through the writer slot with ADR-023 §2's extent guard (n <= 0 -> empty Vec at the trait-impl entry), the full JobHandle impl (one-shot ack/retry/fail as 'static boxed futures, repeatable absolute reset heartbeat, substrate's uniform validity predicate), and the engine-owned equal-jitter exponential backoff (std-only RandomState hash jitter, integerized inclusive [ceil(half), cap], 1-hour cap; no rand dep - documented). ack_batch loses its substrate worker filter (register D-31 - ADR-019 §1's worker-less batch form); job_from_json decode + stamps_with_ override moved to their one owners (queue.rs / resolution.rs); reader-pool helpers lifted from stream.rs into seam.rs. Store::queue wired; 10 acceptance tests (lifecycle/stamps, extent guard, exactly-once under concurrency, backoff range + cap + override, validity predicate + reclaim, dead-letter defaults + get_job visibility, cancel, ack_batch, sweep both-states + retention, validation + closed-store). Workspace 25+3+171 green, clippy -D warnings, fmt clean; sqlite suite 4x green.
This commit is contained in:
1 parent
4913302b47
commit
513df0b311
11 files changed
+1708
-195
No files matched your search
@@ -24,17 +24,18 @@
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
use alkstore::{BoxedFuture, EnqueueOpts, Error, Job, JobState, Result, StreamEvent, TxHandle};
|
||||
use alkstore::{BoxedFuture, EnqueueOpts, Error, Job, Result, StreamEvent, TxHandle};
|
||||
use alkstore::{validate_local_name, validate_shared_name};
|
||||
|
||||
use crate::queue::job_from_json;
|
||||
use crate::resolution::{
|
||||
outbox_backing_queue_name, outbox_default_stamps, plain_queue_default_stamps,
|
||||
resolve_enqueue_opts,
|
||||
resolve_enqueue_opts, stamps_with_override,
|
||||
};
|
||||
use crate::seam::{blocking, sqlite_error};
|
||||
use crate::stream::stream_events_from_json_page;
|
||||
use crate::substrate::{
|
||||
Stamps, Writer, enqueue, get_job, stream_get_offset, stream_publish, stream_read_since,
|
||||
Writer, enqueue, get_job, stream_get_offset, stream_publish, stream_read_since,
|
||||
stream_save_offset,
|
||||
};
|
||||
|
||||
@@ -164,18 +165,6 @@ impl Drop for ConnLease {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stamp resolution for the tx path over the handle-less default
|
||||
/// shapes: `EnqueueOpts::max_attempts` is the one per-job override,
|
||||
/// resolved over the base stamp set (ADR-010 §3a; the other stamps
|
||||
/// take the base untouched — the derived defaults carry
|
||||
/// visibility/backoff/retention).
|
||||
fn stamps_with_override(base: Stamps, opts: &EnqueueOpts) -> Stamps {
|
||||
Stamps {
|
||||
max_attempts: opts.max_attempts.unwrap_or(base.max_attempts),
|
||||
..base
|
||||
}
|
||||
}
|
||||
|
||||
impl TxHandle for SqliteTxHandle {
|
||||
fn enqueue_tx<'a>(
|
||||
&'a mut self,
|
||||
@@ -428,67 +417,3 @@ impl Drop for SqliteTxHandle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the substrate's job-row JSON into the contract value
|
||||
/// (`Job::from_row` — the engine-side constructor, ADR-021 §2's
|
||||
/// `claimed_at` surfaced).
|
||||
fn job_from_json(value: &serde_json::Value) -> Result<Option<Job>> {
|
||||
let state = match value["state"].as_str() {
|
||||
Some("pending") => JobState::Pending,
|
||||
Some("processing") => JobState::Processing,
|
||||
Some("dead") => JobState::Dead,
|
||||
other => {
|
||||
return Err(Error::Codec(format!(
|
||||
"job row state must be pending|processing|dead, got {other:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let payload: Vec<u8> = value["payload"]
|
||||
.as_str()
|
||||
.ok_or_else(|| Error::Codec("job row payload must be a string".to_string()))?
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
let field = |name: &str| -> Result<i64> {
|
||||
serde_json::from_value(value[name].clone())
|
||||
.map_err(|e| Error::Codec(format!("job row {name} must be i64: {e}")))
|
||||
};
|
||||
let opt_field = |name: &str| -> Result<Option<i64>> {
|
||||
if value[name].is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
serde_json::from_value(value[name].clone())
|
||||
.map_err(|e| Error::Codec(format!("job row {name} must be i64: {e}")))
|
||||
}
|
||||
};
|
||||
let opt_str = |name: &str| -> Result<Option<String>> {
|
||||
if value[name].is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
serde_json::from_value(value[name].clone())
|
||||
.map_err(|e| Error::Codec(format!("job row {name} must be a string: {e}")))
|
||||
}
|
||||
};
|
||||
Ok(Some(Job::from_row(
|
||||
field("id")?,
|
||||
value["queue"]
|
||||
.as_str()
|
||||
.ok_or_else(|| Error::Codec("job row queue must be a string".to_string()))?
|
||||
.to_string(),
|
||||
state,
|
||||
payload,
|
||||
field("priority")?,
|
||||
field("run_at")?,
|
||||
field("attempts")?,
|
||||
field("max_attempts")?,
|
||||
opt_str("worker_id")?,
|
||||
opt_field("claimed_at")?,
|
||||
opt_field("claim_expires_at")?,
|
||||
field("created_at")?,
|
||||
opt_field("expires_at")?,
|
||||
field("visibility_timeout_s")?,
|
||||
field("backoff_base_s")?,
|
||||
opt_field("dead_letter_retention_s")?,
|
||||
opt_str("last_error")?,
|
||||
opt_field("died_at")?,
|
||||
)))
|
||||
}
|
||||
Reference in new issue
Block a user