diff --git a/Cargo.lock b/Cargo.lock index d7705d2cbe09e..9cf0704568a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,31 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "antithesis-instrumentation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6b548668212c6d3a942a4ac7be13bc4fc25715bf661328438f30e3fd342cf0" +dependencies = [ + "cc", +] + +[[package]] +name = "antithesis_sdk" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" +dependencies = [ + "libc", + "libloading", + "linkme", + "once_cell", + "rand 0.8.6", + "rustc_version_runtime", + "serde", + "serde_json", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -6585,6 +6610,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "linkme" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b" +dependencies = [ + "proc-macro2 1.0.106", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -13103,6 +13148,8 @@ dependencies = [ name = "vector-buffers" version = "0.1.0" dependencies = [ + "antithesis-instrumentation", + "antithesis_sdk", "async-recursion", "async-stream", "async-trait", @@ -13130,6 +13177,7 @@ dependencies = [ "rand 0.9.4", "rkyv", "serde", + "serde_json", "serde_yaml", "snafu 0.9.0", "temp-dir", diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 2d00edc12d4dd..03a0153e54701 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -18,6 +18,8 @@ anstyle,https://github.com/rust-cli/anstyle,MIT OR Apache-2.0,The anstyle Author anstyle-parse,https://github.com/rust-cli/anstyle,MIT OR Apache-2.0,The anstyle-parse Authors anstyle-query,https://github.com/rust-cli/anstyle,MIT OR Apache-2.0,The anstyle-query Authors anstyle-wincon,https://github.com/rust-cli/anstyle,MIT OR Apache-2.0,The anstyle-wincon Authors +antithesis-instrumentation,https://github.com/antithesishq/antithesis-instrumentation-rust,MIT,The antithesis-instrumentation Authors +antithesis_sdk,https://github.com/antithesishq/antithesis-sdk-rust,MIT,The antithesis_sdk Authors anyhow,https://github.com/dtolnay/anyhow,MIT OR Apache-2.0,David Tolnay apache-avro,https://github.com/apache/avro-rs,Apache-2.0,The apache-avro Authors arbitrary,https://github.com/rust-fuzz/arbitrary,MIT OR Apache-2.0,"The Rust-Fuzz Project Developers, Nick Fitzgerald , Manish Goregaokar , Simonas Kazlauskas , Brian L. Troutwine , Corey Farwell " @@ -427,12 +429,15 @@ lexical-util,https://github.com/Alexhuszagh/rust-lexical,MIT OR Apache-2.0,Alex lexical-write-float,https://github.com/Alexhuszagh/rust-lexical,MIT OR Apache-2.0,Alex Huszagh lexical-write-integer,https://github.com/Alexhuszagh/rust-lexical,MIT OR Apache-2.0,Alex Huszagh libc,https://github.com/rust-lang/libc,MIT OR Apache-2.0,The Rust Project Developers +libloading,https://github.com/nagisa/rust_libloading,ISC,Simonas Kazlauskas libm,https://github.com/rust-lang/libm,MIT OR Apache-2.0,Jorge Aparicio libsqlite3-sys,https://github.com/rusqlite/rusqlite,MIT,The rusqlite developers libz-sys,https://github.com/rust-lang/libz-sys,MIT OR Apache-2.0,"Alex Crichton , Josh Triplett , Sebastian Thiel " line-clipping,https://github.com/joshka/line-clipping,MIT OR Apache-2.0,Josh McKinney linked-hash-map,https://github.com/contain-rs/linked-hash-map,MIT OR Apache-2.0,"Stepan Koltsov , Andrew Paseltiner " linked_hash_set,https://github.com/alexheretic/linked-hash-set,Apache-2.0,Alex Butler +linkme,https://github.com/dtolnay/linkme,MIT OR Apache-2.0,David Tolnay +linkme-impl,https://github.com/dtolnay/linkme,MIT OR Apache-2.0,David Tolnay linux-raw-sys,https://github.com/sunfishcode/linux-raw-sys,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Dan Gohman listenfd,https://github.com/mitsuhiko/listenfd,Apache-2.0,Armin Ronacher litemap,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers diff --git a/lib/vector-buffers/src/lib.rs b/lib/vector-buffers/src/lib.rs index 5f6ade35f9c78..ef969a5142ac0 100644 --- a/lib/vector-buffers/src/lib.rs +++ b/lib/vector-buffers/src/lib.rs @@ -14,6 +14,10 @@ #[macro_use] extern crate tracing; +// Keep the Antithesis LLVM coverage-instrumentation runtime shim linked into any +// binary that uses this crate (e.g. the Vector SUT). No effect outside Antithesis. +use antithesis_instrumentation as _; + mod buffer_usage_data; pub mod config; diff --git a/lib/vector-buffers/src/variants/disk_v2/ledger.rs b/lib/vector-buffers/src/variants/disk_v2/ledger.rs index e63fc5fb16973..c1ec8c7484b5d 100644 --- a/lib/vector-buffers/src/variants/disk_v2/ledger.rs +++ b/lib/vector-buffers/src/variants/disk_v2/ledger.rs @@ -29,6 +29,37 @@ use crate::buffer_usage_data::BufferUsageHandle; pub const LEDGER_LEN: usize = align16(mem::size_of::()); +/// SUT-side Antithesis assertion: the in-memory `total_buffer_size` accounting +/// atomic is never decremented by more than its current value. A violation is +/// the root of Vector #21683 — the unsaturated `fetch_sub` wraps toward 2^64, +/// `is_buffer_full()` then returns true forever, and the writer deadlocks. +/// Isolated in its own function with broad `allow`s because `lib.rs` sets +/// `#![deny(warnings)]` + `#![deny(clippy::pedantic)]` and the assertion-macro +/// expansion need not satisfy those. No-op outside Antithesis. +#[inline] +#[allow(warnings, clippy::all, clippy::pedantic)] +fn assert_total_buffer_size_no_underflow(current: u64, amount: u64) { + antithesis_sdk::assert_always!( + amount <= current, + "ledger total_buffer_size decrement never underflows (root of #21683)" + ); +} + +/// SUT-side Antithesis assertion: `get_total_records` computes the count as +/// `next_writer_id.wrapping_sub(last_reader_id) - 1`. When the wrapped +/// difference is `0` (a fully drained buffer where the reader has caught up to +/// the writer's next id), the trailing `- 1` underflows to ~2^64 — a bogus +/// ~18-quintillion buffered-records reading. The invariant is that the wrapped +/// difference is always at least 1. No-op outside Antithesis. +#[inline] +#[allow(warnings, clippy::all, clippy::pedantic)] +fn assert_total_records_no_underflow(next_writer_id: u64, last_reader_id: u64) { + antithesis_sdk::assert_always!( + next_writer_id.wrapping_sub(last_reader_id) >= 1, + "ledger get_total_records never underflows on a drained buffer (#21683 metrics)" + ); +} + /// Error that occurred during calls to [`Ledger`]. #[derive(Debug, Snafu)] pub enum LedgerLoadCreateError { @@ -263,6 +294,7 @@ where let next_writer_id = self.state().get_next_writer_record_id(); let last_reader_id = self.state().get_last_reader_record_id(); + assert_total_records_no_underflow(next_writer_id, last_reader_id); next_writer_id.wrapping_sub(last_reader_id) - 1 } @@ -289,6 +321,10 @@ where /// Decrements the total number of bytes for all unread records in the buffer. pub fn decrement_total_buffer_size(&self, amount: u64) { + assert_total_buffer_size_no_underflow( + self.total_buffer_size.load(Ordering::Acquire), + amount, + ); let last_total_buffer_size = self.total_buffer_size.fetch_sub(amount, Ordering::AcqRel); trace!( previous_buffer_size = last_total_buffer_size, diff --git a/lib/vector-buffers/src/variants/disk_v2/reader.rs b/lib/vector-buffers/src/variants/disk_v2/reader.rs index 8f15bfedb9fff..79e9ed285415f 100644 --- a/lib/vector-buffers/src/variants/disk_v2/reader.rs +++ b/lib/vector-buffers/src/variants/disk_v2/reader.rs @@ -27,6 +27,22 @@ use crate::{ variants::disk_v2::{io::AsyncFile, record::try_as_record_archive}, }; +/// SUT-side Antithesis assertion: when accounting for a partially-read or +/// truncated data file, `delete_completed_data_file` computes the buffer-size +/// adjustment as `metadata.len() - bytes_read`. If the on-disk file is shorter +/// than the bytes we believe we read (a torn tail after a crash, or filesystem +/// fault), this unsaturated subtraction underflows to ~2^64 and is then fed +/// into `decrement_total_buffer_size` — a #21683-class wrap. The invariant is +/// `bytes_read <= metadata.len()`. No-op outside Antithesis. +#[inline] +#[allow(warnings, clippy::all, clippy::pedantic)] +fn assert_data_file_size_delta_no_underflow(file_len: u64, bytes_read: u64) { + antithesis_sdk::assert_always!( + bytes_read <= file_len, + "reader data-file size delta never underflows (reader.rs:524, #21683-class)" + ); +} + pub(super) struct ReadToken { record_id: u64, record_bytes: usize, @@ -521,6 +537,7 @@ where let decrease_amount = bytes_read.map_or_else( || metadata.len(), |bytes_read| { + assert_data_file_size_delta_no_underflow(metadata.len(), bytes_read); let size_delta = metadata.len() - bytes_read; if size_delta > 0 { debug!(