swactor/crates/data-plane/tests/byte_ring_guarantees.rs
Zachery Aaron Shores-Chmielewski cd870d3141 feat(data-plane): complete virtual blob namespace
Add durable namespace control, Iroh-backed source transfer, session-independent publication lifetime, and wire managed Myelin jobs and bindings through the reusable data-plane API.
2026-08-22 21:16:56 +04:00

472 lines
17 KiB
Rust

//! Byte-ring guarantees (behavior invariants, not implementation shape).
//!
//! Written before the implementation: every test here is failing until
//! `byte_ring` lands. Each test names the property it defends from the
//! protocol list in `src/byte_ring.rs`.
#![cfg(target_os = "linux")]
use data_plane::arena::{ArenaConfig, ArenaManager, NodeId};
use data_plane::byte_ring::{
self, ByteRingSpec, FlowError, HeaderError, RecordKind, Role, attach, install,
};
fn arena() -> ArenaManager {
ArenaManager::boot(ArenaConfig {
node_id: NodeId(10),
reservation_ceiling: 1 << 20,
base_alignment: 64,
})
.expect("test arena must boot")
}
fn spec(capacity: u64, generation: u64) -> ByteRingSpec {
ByteRingSpec {
capacity,
generation,
alignment: 64,
request_id: 1,
}
}
fn installed(capacity: u64, generation: u64) -> (ArenaManager, byte_ring::RingHandle) {
let mut arena = arena();
let handle = install(&mut arena, spec(capacity, generation)).expect("ring must install");
(arena, handle)
}
fn scribble_u64(arena: &ArenaManager, handle: &byte_ring::RingHandle, off: u64, value: u64) {
arena
.write_arena(handle.offset + off, &value.to_le_bytes())
.expect("scribble");
}
/// Endpoints must be thread-safe enough to hand one side to another thread
/// (property P1's concurrency test depends on this).
#[test]
fn endpoints_are_send() {
fn assert_send<T: Send>() {}
assert_send::<byte_ring::Endpoint>();
}
// ─── install ─────────────────────────────────────────────────────────────────
/// B10-style: after `install` returns, the header is fully written.
#[test]
fn install_writes_valid_header_and_zeroed_data() {
use data_plane::byte_ring::{
OFF_CAPACITY, OFF_COMMIT, OFF_CONSUME, OFF_GENERATION, OFF_MAGIC, OFF_VERSION, RING_MAGIC,
RING_VERSION,
};
let (arena, handle) = installed(4096, 7);
assert_eq!(handle.capacity, 4096);
assert_eq!(handle.generation, 7);
let page = arena.read_arena(handle.offset, 128).expect("read header");
let u32_at = |off: usize| u32::from_le_bytes(page[off..off + 4].try_into().unwrap());
let u16_at = |off: usize| u16::from_le_bytes(page[off..off + 2].try_into().unwrap());
let u64_at = |off: usize| u64::from_le_bytes(page[off..off + 8].try_into().unwrap());
assert_eq!(u32_at(OFF_MAGIC as usize), RING_MAGIC);
assert_eq!(u16_at(OFF_VERSION as usize), RING_VERSION);
assert_eq!(u64_at(OFF_CAPACITY as usize), 4096);
assert_eq!(u64_at(OFF_GENERATION as usize), 7);
assert_eq!(u64_at(OFF_COMMIT as usize), 0);
assert_eq!(u64_at(OFF_CONSUME as usize), 0);
assert!(page[40..128].iter().all(|&b| b == 0), "reserved bytes");
let data = arena
.read_arena(handle.offset + 128, 64)
.expect("read data");
assert!(data.iter().all(|&b| b == 0), "fresh data region is zero");
}
#[test]
fn install_rejects_bad_specs() {
let mut arena = arena();
assert!(matches!(
install(&mut arena, spec(0, 1)).unwrap_err(),
byte_ring::InstallError::ZeroCapacity
));
assert!(matches!(
install(&mut arena, spec(64, 0)).unwrap_err(),
byte_ring::InstallError::ZeroGeneration
));
assert!(matches!(
install(&mut arena, spec(1 << 21, 1)).unwrap_err(),
byte_ring::InstallError::LeaseRejected(_)
));
}
// ─── attach: untrusted-header containment (P5) ──────────────────────────────
type CorruptHeaderCase = (Option<(u64, u64)>, (u64, u64), HeaderError);
#[test]
fn attach_rejects_corrupt_headers_without_trusting_them() {
let cases: Vec<CorruptHeaderCase> = vec![
// (optional pre-scribble, (field, value), expected)
(
None,
(byte_ring::OFF_MAGIC, 0xDEAD_BEEF),
HeaderError::BadMagic { found: 0xDEAD_BEEF },
),
(
None,
(byte_ring::OFF_VERSION, 2),
HeaderError::UnsupportedVersion {
found: 2,
supported: 1,
},
),
(
None,
(byte_ring::OFF_CAPACITY, 999),
HeaderError::CapacityMismatch {
header: 999,
handle: 512,
},
),
(
None,
(byte_ring::OFF_GENERATION, 4),
HeaderError::GenerationMismatch {
header: 4,
handle: 3,
},
),
(
Some((byte_ring::OFF_CONSUME, 10)),
(byte_ring::OFF_COMMIT, 5),
HeaderError::CommitBelowConsume {
commit: 5,
consume: 10,
},
),
(
None,
(byte_ring::OFF_COMMIT, 600),
HeaderError::ReadableExceedsCapacity {
commit: 600,
consume: 0,
capacity: 512,
},
),
];
for (pre, (field, value), expected) in cases {
let (fresh_arena, fresh_handle) = installed(512, 3);
if let Some((pre_field, pre_value)) = pre {
scribble_u64(&fresh_arena, &fresh_handle, pre_field, pre_value);
}
scribble_u64(&fresh_arena, &fresh_handle, field, value);
let error = attach(&fresh_arena, fresh_handle, Role::Producer)
.expect_err("corrupt header must fail attach");
assert_eq!(
error,
byte_ring::AttachError::Header(expected),
"field {field} = {value}"
);
}
}
// ─── movement: exactly-once, in order (P1, P2) ───────────────────────────────
#[test]
fn basic_round_trip_preserves_bytes() {
let (arena, handle) = installed(4096, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
producer
.send_record(RecordKind::Data, b"weights-bytes")
.expect("send");
let received = consumer.recv_record().expect("recv");
assert_eq!(
received,
Some((RecordKind::Data, b"weights-bytes".to_vec()))
);
assert_eq!(consumer.recv_record().expect("recv empty"), None);
}
#[test]
fn wraparound_preserves_stream_exactly() {
// Capacity 128 forces many wraps; move 16 KiB through in odd chunks.
let (arena, handle) = installed(128, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
let expected: Vec<u8> = (0..16_u32 * 1024).map(|i| (i % 251) as u8).collect();
let mut sent = 0;
let mut received = Vec::new();
// Chunk sizes chosen to straddle the capacity and force partial state;
// every chunk must satisfy 5-byte record header + chunk <= 128.
let chunks = [3usize, 123, 1, 100, 65, 64, 122, 17];
let mut chunk_index = 0;
while received.len() < expected.len() {
let chunk = chunks[chunk_index % chunks.len()];
chunk_index += 1;
let end = (sent + chunk).min(expected.len());
// Producer sends what fits, drains when the ring is full.
while producer
.send_record(RecordKind::Data, &expected[sent..end])
.is_err()
{
match consumer.recv_record().expect("recv") {
Some((RecordKind::Data, bytes)) => received.extend_from_slice(&bytes),
Some((kind, _)) => panic!("unexpected record kind {kind:?}"),
None => panic!("deadlock: producer blocked, consumer empty"),
}
}
sent = end;
while let Some((RecordKind::Data, bytes)) = consumer.recv_record().expect("recv") {
received.extend_from_slice(&bytes);
}
}
assert_eq!(received, expected, "byte stream must survive wraparound");
}
#[test]
fn concurrent_producer_consumer_delivers_exactly_once() {
use std::thread::scope;
let (arena, handle) = installed(1024, 1);
let expected: Vec<(RecordKind, Vec<u8>)> = {
let mut rng = 0x5357_4752_1111_u64; // xorshift, deterministic
let mut records = Vec::new();
let mut total = 0;
while total < 8 * 1024 {
let len = ((rng & 0xFF) as usize).max(1);
let bytes: Vec<u8> = (0..len).map(|i| ((total + i) % 251) as u8).collect();
total += len;
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
records.push((RecordKind::Data, bytes));
}
records.push((RecordKind::Eof, Vec::new()));
records
};
let producer_handle = handle;
let consumer_handle = handle;
let producer_records = &expected;
let received: parking_lot::Mutex<Vec<(RecordKind, Vec<u8>)>> =
parking_lot::Mutex::new(Vec::new());
let mut producer = attach(&arena, producer_handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, consumer_handle, Role::Consumer).expect("consumer");
scope(|s| {
let producer_thread = s.spawn(move || {
for (kind, bytes) in producer_records {
// Retry until space frees up: the consumer runs concurrently.
while producer.send_record(*kind, bytes).is_err() {}
}
});
let consumer_records = &received;
let consumer_thread = s.spawn(move || {
loop {
match consumer.recv_record().expect("recv") {
Some((RecordKind::Eof, _)) => break,
Some(record) => consumer_records.lock().push(record),
None => continue, // spin: sleep discipline is the binding slice's concern
}
}
});
producer_thread.join().expect("producer thread");
consumer_thread.join().expect("consumer thread");
});
let got = received.into_inner();
assert_eq!(
got,
expected[..expected.len() - 1],
"records must arrive exactly once, in order, under concurrency"
);
}
// ─── backpressure (P7) ───────────────────────────────────────────────────────
#[test]
fn reserve_reports_exact_free_space_and_recovers() {
let (arena, handle) = installed(128, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
let first = producer.reserve(100).expect("reserve fits");
producer.write(&first, &[7; 100]).expect("write");
producer.commit(first).expect("commit");
assert_eq!(
producer.reserve(50).unwrap_err(),
FlowError::InsufficientSpace {
requested: 50,
free: 28
}
);
// Bytes are readable, intact, and consuming frees the space again.
assert_eq!(consumer.readable().expect("readable"), 100);
let bytes = consumer.read(100).expect("read");
assert!(bytes.iter().all(|&b| b == 7));
consumer.consume(100).expect("consume");
assert_eq!(producer.reserve(50).expect("reserve recovers").len, 50);
}
// ─── completion semantics (P9) ───────────────────────────────────────────────
#[test]
fn records_delimit_completion_distinctly() {
let (arena, handle) = installed(4096, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
producer.send_record(RecordKind::Data, b"a").expect("send");
producer.send_record(RecordKind::Data, b"b").expect("send");
producer.send_record(RecordKind::Eof, b"").expect("send");
assert_eq!(
consumer.recv_record().expect("recv"),
Some((RecordKind::Data, b"a".to_vec()))
);
assert_eq!(
consumer.recv_record().expect("recv"),
Some((RecordKind::Data, b"b".to_vec()))
);
assert_eq!(
consumer.recv_record().expect("recv"),
Some((RecordKind::Eof, Vec::new()))
);
assert_eq!(consumer.recv_record().expect("post-eof"), None);
// Fault is a distinct terminal, not a second EOF.
producer
.send_record(RecordKind::Fault, b"reason")
.expect("send");
assert_eq!(
consumer.recv_record().expect("recv fault"),
Some((RecordKind::Fault, b"reason".to_vec()))
);
}
#[test]
fn torn_records_stay_invisible_until_committed() {
let (arena, handle) = installed(512, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
// Frame the record manually so only the commit is withheld.
let mut framed = Vec::with_capacity(5 + 64);
framed.push(1_u8); // RecordKind::Data
framed.extend_from_slice(&64_u32.to_le_bytes());
framed.extend_from_slice(&[9; 64]);
let reservation = producer.reserve(69).expect("reserve");
producer.write(&reservation, &framed).expect("write");
// Deliberately do NOT commit.
assert_eq!(consumer.readable().expect("readable"), 0);
assert_eq!(consumer.recv_record().expect("torn invisible"), None);
producer.commit(reservation).expect("commit");
assert_eq!(consumer.readable().expect("readable"), 69);
let (kind, bytes) = consumer.recv_record().expect("recv").unwrap();
assert_eq!(kind, RecordKind::Data);
assert!(bytes.iter().all(|&b| b == 9));
}
// ─── generation fence (P10) ──────────────────────────────────────────────────
#[test]
fn stale_reservations_are_rejected_after_generation_change() {
let (arena, handle) = installed(512, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let reservation = producer.reserve(32).expect("reserve");
// The ring is replaced under us: generation moves on.
scribble_u64(&arena, &handle, byte_ring::OFF_GENERATION, 2);
assert_eq!(
producer.commit(reservation).unwrap_err(),
FlowError::StaleReservation {
reservation: 1,
ring: 2
}
);
}
// ─── role enforcement (P3) ───────────────────────────────────────────────────
#[test]
fn wrong_role_operations_are_rejected() {
let (arena, handle) = installed(512, 1);
let mut producer = attach(&arena, handle, Role::Producer).expect("producer");
let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
assert_eq!(
consumer.reserve(8).unwrap_err(),
FlowError::RoleViolation {
operation: "reserve",
role: Role::Consumer
}
);
assert_eq!(
consumer
.commit(byte_ring::Reservation {
start: 0,
len: 0,
generation: 1,
})
.unwrap_err(),
FlowError::RoleViolation {
operation: "commit",
role: Role::Consumer
}
);
assert_eq!(
producer.consume(8).unwrap_err(),
FlowError::RoleViolation {
operation: "consume",
role: Role::Producer
}
);
assert_eq!(
producer.readable().unwrap_err(),
FlowError::RoleViolation {
operation: "readable",
role: Role::Producer
}
);
}
// ─── mid-protocol revalidation (P5) ──────────────────────────────────────────
#[test]
fn operations_revalidate_cursors_and_never_panic() {
let (arena, handle) = installed(512, 1);
let producer = attach(&arena, handle, Role::Producer).expect("producer");
let consumer = attach(&arena, handle, Role::Consumer).expect("consumer");
// Corrupt the cursors mid-protocol (consume beyond commit).
scribble_u64(&arena, &handle, byte_ring::OFF_CONSUME, 5);
assert_eq!(
producer.reserve(8).unwrap_err(),
FlowError::Corrupt(HeaderError::CommitBelowConsume {
commit: 0,
consume: 5
})
);
assert_eq!(
consumer.readable().unwrap_err(),
FlowError::Corrupt(HeaderError::CommitBelowConsume {
commit: 0,
consume: 5
})
);
// An impossible readable span is rejected at attach, not crashed on.
let (arena2, handle2) = installed(512, 1);
scribble_u64(&arena2, &handle2, byte_ring::OFF_COMMIT, 600);
assert_eq!(
attach(&arena2, handle2, Role::Consumer).unwrap_err(),
byte_ring::AttachError::Header(HeaderError::ReadableExceedsCapacity {
commit: 600,
consume: 0,
capacity: 512
})
);
}