use super::*;
use crate::background::CheckpointWorkProbe;
use hawdb_core::{RuntimeMemoryError, RuntimeTaskContext};
use hawdb_qos::{
IoConcurrencyBudget, LocalQosPolicy, LocalQosScheduler, RuntimeGovernor, RuntimeGovernorConfig,
RuntimeMemorySnapshot, RuntimeResourceBudget, RuntimeResourceSnapshot, RuntimeWorkRequest,
};
use std::io::Write;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
fn collect(
payload: &[u8],
generation: u64,
position: u64,
work: &CheckpointWorkContext,
) -> std::result::Result<Vec<u8>, CheckpointWorkError> {
let mut stream = CheckpointWalFrameStream::new(generation, payload, position, work)?;
let mut output = Vec::with_capacity(stream.encoded_len());
while let Some(fragment) = stream.next()? {
assert!(fragment.encoded_len() < WAL_BLOCK_BYTES + WAL_FRAGMENT_HEADER_BYTES);
for part in fragment.parts() {
output.extend_from_slice(part);
}
}
assert_eq!(output.len(), stream.encoded_len());
Ok(output)
}
fn scheduler() -> LocalQosScheduler {
LocalQosScheduler::new(LocalQosPolicy {
max_background_operations: Some(1),
max_total_background_operations: Some(1),
..Default::default()
})
}
#[test]
fn checkpoint_units_wal_stream_matches_every_position_and_fragment_boundary() {
let local = scheduler();
let work = CheckpointWorkContext::default().with_scheduler(local.clone());
for position in 0..WAL_BLOCK_BYTES as u64 {
assert_eq!(
collect(&[0, 1, 255, 3, 0, 5, 9], 17, position, &work).unwrap(),
frame_binary_wal_record(17, &[0, 1, 255, 3, 0, 5, 9], position)
);
}
for length in [
0,
1,
14,
15,
32_752,
32_753,
32_754,
65_536,
5 * WAL_BLOCK_BYTES + 7,
] {
let payload: Vec<_> = (0..length).map(|i| ((i * 71) % 256) as u8).collect();
for position in [0, 1, 32_752, 32_753, 32_754, 32_767, u64::MAX] {
for generation in [0, 1, u64::MAX] {
assert_eq!(
collect(&payload, generation, position, &work).unwrap(),
frame_binary_wal_record(generation, &payload, position)
);
}
}
}
assert_eq!(local.snapshot().running_background_operations, 0);
}
#[test]
fn checkpoint_units_wal_stream_cancels_every_fragment_and_retries_same_source() {
let payload: Vec<_> = (0..3 * 64 * 1024 + 7)
.map(|i| ((i * 71) % 256) as u8)
.collect();
let expected = frame_binary_wal_record(31, &payload, 32_767);
let local = scheduler();
let baseline = Arc::new(CheckpointWorkProbe::default());
assert_eq!(
collect(&payload, 31, 32_767, &baseline.context(local.clone())).unwrap(),
expected
);
let count = baseline.completed.load(Ordering::SeqCst);
assert!(count > 1);
for cut in 1..=count {
let probe = Arc::new(CheckpointWorkProbe::default());
probe.cancel_after.store(cut, Ordering::SeqCst);
assert!(matches!(
collect(&payload, 31, 32_767, &probe.context(local.clone())),
Err(CheckpointWorkError::Stopped(_))
));
probe.assert_released(&local);
let retry = Arc::new(CheckpointWorkProbe::default());
assert_eq!(
collect(&payload, 31, 32_767, &retry.context(local.clone())).unwrap(),
expected
);
retry.assert_released(&local);
}
}
#[test]
fn checkpoint_units_wal_stream_writes_native_output_with_one_byte_working_reservation() {
let payload: Vec<_> = (0..257 * 1024 + 7)
.map(|i| ((i * 17) % 256) as u8)
.collect();
let governor = RuntimeGovernor::new(
RuntimeGovernorConfig {
memory_budget_bytes: Some(1),
background_task_limit: Some(NonZeroUsize::MIN),
..RuntimeGovernorConfig::shared_host()
},
RuntimeResourceSnapshot::from_parts(
RuntimeResourceBudget::from_limits(NonZeroUsize::MIN, None, None),
RuntimeMemorySnapshot::from_limits(Some(1 << 30), Some(1 << 30), None, None, None),
),
IoConcurrencyBudget::new(2, 1),
);
let permit = governor
.try_admit(RuntimeWorkRequest::background_maintenance(1).with_io_wave_slots(1))
.unwrap();
let local = scheduler();
let task = permit.bind_task_context(RuntimeTaskContext::default());
let work = CheckpointWorkContext::new(task.clone()).with_scheduler(local.clone());
let path = std::env::temp_dir().join(format!(
"hawdb-wal-stream-{}",
hawdb_core::generate_uuidv7().unwrap()
));
let mut file = crate::file_io::File::create(&path).unwrap();
let mut stream = CheckpointWalFrameStream::new(23, &payload, 32_767, &work).unwrap();
while let Some(fragment) = stream.next().unwrap() {
let wave = work.io_wave().unwrap().expect("real governor wave");
assert_eq!(governor.snapshot().active_background_io_slots, 1);
for part in fragment.parts() {
file.write_all(part).unwrap();
}
drop(wave);
}
file.sync_all().unwrap();
drop(file);
assert_eq!(
std::fs::read(&path).unwrap(),
frame_binary_wal_record(23, &payload, 32_767)
);
assert!(matches!(
task.reserve_working_memory(1),
Err(RuntimeMemoryError::ReservationExceeded {
available_bytes: 1,
..
})
));
assert_eq!(local.snapshot().running_background_operations, 0);
assert_eq!(governor.snapshot().active_background_io_slots, 0);
drop(work);
drop(task);
drop(permit);
assert_eq!(governor.snapshot().admitted_memory_bytes, 0);
std::fs::remove_file(path).unwrap();
}
#[test]
#[ignore = "Local-only seeded differential campaign; run through the Bazel fuzz target"]
fn checkpoint_wal_verbatim_stream_differential_campaign() {
let local = scheduler();
let work = CheckpointWorkContext::default().with_scheduler(local.clone());
let mut state = 0x7d83_4cb2_948a_e605_u64;
for case in 0..1024 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let position = state;
let generation = state.rotate_left(23);
let length = (state as usize % (8 * WAL_BLOCK_BYTES)) + case % 17;
let payload: Vec<_> = (0..length)
.map(|i| (state.wrapping_add(i as u64).rotate_left((i % 63) as u32) >> 31) as u8)
.collect();
let actual = collect(&payload, generation, position, &work).unwrap();
assert_eq!(
actual,
frame_binary_wal_record(generation, &payload, position),
"case {case}"
);
let mut bytes =
vec![0; WAL_BINARY_FILE_HEADER_BYTES + (position % WAL_BLOCK_BYTES as u64) as usize];
bytes.extend_from_slice(&actual);
let from = (WAL_BINARY_FILE_HEADER_BYTES as u64) + position % WAL_BLOCK_BYTES as u64;
let to = bytes.len() as u64;
let mut reader =
BinaryWalReader::range(std::io::Cursor::new(bytes), generation, None, from, to)
.unwrap();
match reader.next_event().unwrap() {
BinaryWalReadEvent::Record {
payload: decoded, ..
} => assert_eq!(decoded, payload),
_ => panic!("case {case}: complete stream must decode"),
}
assert!(matches!(
reader.next_event().unwrap(),
BinaryWalReadEvent::Eof
));
}
assert_eq!(local.snapshot().running_background_operations, 0);
}