general review 004 (pre-release, whole workspace): one HIGH slot-poisoning fix, two contract-observable divergence fixes, robustness + doc corrections — all gates re-run green
- sqlite scheduler tick: a failed COMMIT (or ROLLBACK) left the connection released with a stuck-open transaction, wedging the writer slot forever — the one commit site missing the drop-+-reopen-replenish arm; tick now takes the conn by value with a poisoned disposition (drop + replenish), the commit-fault seam reaches the tick, and failed_tick_commit_replenishes_the_writer_slot pins the arm - pg save_offset_tx stored GREATEST($3,0) where the wave-8 gate's negative-seed fix (raw seed, ADR-023 §2 2026-10-11 annotation) covered only the auto-commit form; mem's merged in-tx read clamped a staged negative seed against a phantom zero; both fixed and the tx-form totality pinned by a new stamped suite leg (in_tx_reads_see_own_writes) green on all three columns vs the live harness server - pg: a failed forwarder register leaked the channel refcount forever (no receiver to drop it) — listen/spawn_bridge now unregister on the register error, mirroring the subscribe-failure arm; register doc corrected to put the failure disposition at the call site - pg forwarder: nine poisoned-mutex expect() panics converted to the mem crate's into_inner recovery idiom (no-expect-outside-tests rule) - core encode_payload: a deeply-nested Value aborted the process in serde_json's unguarded serializer recursion and the fuzz pin blessed storing bytes no decoder could read back; encode now rejects nesting past 127 containers (the empirically-verified decode limit) with typed Codec — decode-aligned boundary pins added to the fuzz invariant and value_type_tests, applied pre-publish within the open class-1 window - scheduler sleep deadlines: Instant + Duration overflow caps (MAX_SLEEP_S, tick-and-resleep) on all three engines for far-future boundaries; sqlite tick claim stamp now saturating_add - docs read true again: Error::Closed scope (stream try_recv arm), validation whitespace wording, sqlite lib.rs phantom durability knobs, pg lock.rs honest autocommit wording, mem tx/stream merge + wake- granularity wording; wakes.rs stale allow(dead_code) removed - tests: value_type_tests exercised its own map_err instead of the function under test (fixed); unwrap_or_default reference encodes panic with cause - docs/reviews/004-pre-release-general-review.md records nine fixed + sixteen parked findings (sqlite seam panic-safety armor, watcher post-death-subscribe hang + initial-stat self-kill, pg forwarder TOCTOU/hang/diagnosability, pg BIGINT server-side stamp overflow, mem growth/outbox notes, pg identifier-truncation doc note, smaller) and two false positives caught on verification; implementation.md review-rounds entry + core-contract backlog rows updated - gates: build, workspace tests (sqlite 194, core 27, suite legs new), full live pg column 127+25+9, clippy -D warnings, fmt, fuzz replay 6/6, wasm32 check, doc gate -D warnings — all green
This commit is contained in:
1 parent
5385f6bee3
commit
029f885aa1
24 files changed
+738
-103
No files matched your search
@@ -64,6 +64,15 @@ const SLEEP_RENEW_EVERY_S: u64 = 2;
|
||||
/// The idle sleep when nothing is scheduled (soonest = 0).
|
||||
const IDLE_SLEEP_S: u64 = 60;
|
||||
|
||||
/// The upper bound on one sleep slice, in seconds: a schedule whose
|
||||
/// next boundary is further out sleeps this long, ticks (a no-op), and
|
||||
/// sleeps again — the tick recomputes the soonest every iteration.
|
||||
/// The cap keeps `Instant + Duration` arithmetic on the sleep deadline
|
||||
/// inside every platform's `Instant` range (a far-future boundary's
|
||||
/// raw wait can exceed a platform's representable span; a bounded
|
||||
/// duration never does).
|
||||
const MAX_SLEEP_S: u64 = 60 * 60 * 24 * 30;
|
||||
|
||||
/// The bounded catch-up cap (ADR-009 §4 — a fixed contract constant,
|
||||
/// honker parity, not a knob).
|
||||
const MAX_CATCHUP_FIRES: i64 = 64;
|
||||
@@ -327,7 +336,7 @@ async fn keep_awake_renewing(lease: &MemLock, clock: &Clock, stop: &StopToken, s
|
||||
} else {
|
||||
soonest_at.saturating_sub(now).max(1) as u64
|
||||
};
|
||||
let deadline = started + Duration::from_secs(wait_s);
|
||||
let deadline = started + Duration::from_secs(wait_s.min(MAX_SLEEP_S));
|
||||
loop {
|
||||
let now = tokio::time::Instant::now();
|
||||
if stop.is_cancelled() || now >= deadline {
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
//! internal and never compared cross-engine — ADR-015 §4: mem's
|
||||
//! fresh-engine count starts at 1, the SQL engines' counters may
|
||||
//! not), keys
|
||||
//! not), keys
|
||||
//! are carried metadata (a present key must be non-empty —
|
||||
//! `InvalidName`, ADR-015 §1), and the commit boundary fires one
|
||||
//! wake per event on the stream's engine-internal wake channel.
|
||||
//! wake per published event on the stream's engine-internal wake
|
||||
//! channel (the no-handle publish path; a tx commit coalesces to
|
||||
//! one wake per stream — the feed is plumbing, not contract
|
||||
//! surface, and receivers drain by cursor).
|
||||
//! - Cursor reads are `offset ASC` (global FIFO, ADR-015 §4) with the
|
||||
//! extent guard at the trait-impl entry (`limit <= 0` → empty
|
||||
//! `Vec`, ADR-023 §2).
|
||||
@@ -104,6 +108,16 @@ pub(crate) fn get_offset_row(s: &StreamState, stream: &str, consumer: &str) -> i
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Whether a checkpoint row exists for the consumer. The absent read is
|
||||
/// 0 (`get_offset_row`), but the tx merge needs the distinction: a
|
||||
/// staged save on a consumer with no committed row is a *seed* (stored
|
||||
/// raw, per ADR-023 §2's boundary/offset annotation), not a candidate
|
||||
/// against a phantom zero.
|
||||
pub(crate) fn has_offset_row(s: &StreamState, stream: &str, consumer: &str) -> bool {
|
||||
s.offsets
|
||||
.contains_key(&(stream.to_string(), consumer.to_string()))
|
||||
}
|
||||
|
||||
/// `trim_to`: delete `offset <= horizon` (exact boundary); surviving
|
||||
/// events keep their offsets. Returns the deleted count. Wakes nothing.
|
||||
pub(crate) fn trim_events(s: &mut StreamState, stream: &str, horizon: i64) -> i64 {
|
||||
|
||||
+25
-10
@@ -22,9 +22,11 @@
|
||||
//! invisible to every other view until its commit — no isolation
|
||||
//! questions can arise across handles).
|
||||
//! - **Commit-merge + wake-at-commit**: `commit` consumes the handle;
|
||||
//! the merge is one guarded operation (queues → streams, the
|
||||
//! canonical lock order) and the overlay's pending wakes fire in the
|
||||
//! same operation, after the merged state is visible — a tx-staged
|
||||
//! the merge lands queues then streams (the canonical lock order;
|
||||
//! two sequential guarded sections on a non-awaiting path — each
|
||||
//! section's committed view is self-consistent at its boundary) and
|
||||
//! the overlay's pending wakes fire after the merged state is
|
||||
//! visible — a tx-staged
|
||||
//! notify delivers nothing until the merge, one wake per staged
|
||||
//! notify (per-notify, never coalesced). That discipline is what the
|
||||
//! pg engine had to fix retroactively (`pg-fix-tx-wake`); here it is
|
||||
@@ -58,7 +60,7 @@ use crate::resolution::{
|
||||
use crate::state::Guard;
|
||||
use crate::state::{EventRow, JobRow, State};
|
||||
use crate::stream::{
|
||||
get_offset_row, insert_event, read_events, save_offset_row, stream_wake_channel,
|
||||
get_offset_row, has_offset_row, insert_event, read_events, save_offset_row, stream_wake_channel,
|
||||
};
|
||||
use crate::wakes::WakeBus;
|
||||
|
||||
@@ -152,14 +154,27 @@ fn merged_read_since(
|
||||
|
||||
/// The merged checkpoint read (absent everywhere = 0).
|
||||
fn merged_get_offset(overlay: &Overlay, state: &State, stream: &str, consumer: &str) -> i64 {
|
||||
let committed = {
|
||||
let (committed, present) = {
|
||||
let s = state.streams.guard();
|
||||
get_offset_row(&s, stream, consumer)
|
||||
(
|
||||
get_offset_row(&s, stream, consumer),
|
||||
has_offset_row(&s, stream, consumer),
|
||||
)
|
||||
};
|
||||
let staged = overlay
|
||||
.saves
|
||||
.get(&(stream.to_string(), consumer.to_string()));
|
||||
staged.map_or(committed, |s| (*s).max(committed))
|
||||
// The in-tx read shows what the commit would produce: a staged
|
||||
// save against an existing checkpoint rides the monotone guard
|
||||
// (the staged save only merges when it is ahead, so the read is
|
||||
// the max), while a staged save *seeding* an absent checkpoint is
|
||||
// stored raw — a negative seed reads raw in-tx too (ADR-023 §2's
|
||||
// boundary/offset annotation), not clamped against a phantom zero.
|
||||
match staged {
|
||||
Some(s) if present => (*s).max(committed),
|
||||
Some(s) => *s,
|
||||
None => committed,
|
||||
}
|
||||
}
|
||||
|
||||
fn event_of(row: EventRow, stream: &str) -> StreamEvent {
|
||||
@@ -420,9 +435,9 @@ impl MemTxHandle {
|
||||
}
|
||||
|
||||
/// The commit-merge: move every staged effect into the committed state
|
||||
/// under one guarded operation (the canonical queues → streams lock
|
||||
/// order; each section's committed view is self-consistent at its
|
||||
/// boundary), then fire the overlay's pending wakes — the
|
||||
/// (queues then streams, the canonical lock order; two sequential
|
||||
/// guarded sections on a non-awaiting path) then fire the overlay's
|
||||
/// pending wakes — the
|
||||
/// wake-at-commit discipline: nothing delivers until the merge lands.
|
||||
fn merge_and_wake(state: Arc<State>, wakes: Arc<WakeBus>, mut overlay: Overlay) {
|
||||
{
|
||||
|
||||
@@ -33,11 +33,6 @@
|
||||
//! one commit's notify on a channel is one wake per subscribed feed,
|
||||
//! never coalesced (no batching boundary exists to coalesce across).
|
||||
|
||||
// The substrate routes every wave-6 mechanism's commit-boundary
|
||||
// wakes; until the mechanism tasks wire it, the foundations-only
|
||||
// build sees the attach/fire surface as dead.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -89,7 +84,6 @@ impl std::fmt::Debug for WakeBus {
|
||||
}
|
||||
|
||||
impl WakeBus {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
registry: Mutex::new(Registry::default()),
|
||||
|
||||
Reference in new issue
Block a user