extern crate std;
use self::std::{env, fs, process::Command};
use super::*;
use alloc::string::{String, ToString};
const ROWS: u32 = 262_144;
const CHILD_CASE: &str = "PIPECIRCUIT_ALLOCATION_REFUSAL_CASE";
fn prlimit(limit: &str) -> Result<(), String> {
let output = Command::new("prlimit")
.args([
"--pid",
&std::process::id().to_string(),
&std::format!("--as={limit}:"),
])
.output()
.map_err(|error| error.to_string())?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).into_owned())
}
}
fn limited<Output>(additional: usize, run: impl FnOnce() -> Output) -> Result<Output, String> {
let previous = Command::new("prlimit")
.args([
"--pid",
&std::process::id().to_string(),
"--as",
"--output",
"SOFT",
"--noheadings",
])
.output()
.map_err(|error| error.to_string())?;
if !previous.status.success() {
return Err("cannot inspect child address-space limit".into());
}
let previous = String::from_utf8(previous.stdout).map_err(|error| error.to_string())?;
let status = fs::read_to_string("/proc/self/status").map_err(|error| error.to_string())?;
let kib = status
.lines()
.find_map(|line| line.strip_prefix("VmSize:"))
.and_then(|value| value.split_whitespace().next())
.ok_or("missing process virtual size")?
.parse::<usize>()
.map_err(|error| error.to_string())?;
let limit = kib
.checked_mul(1024)
.and_then(|bytes| bytes.checked_add(additional))
.and_then(|bytes| bytes.checked_add(262_144))
.ok_or("limit overflow")?;
prlimit(&limit.to_string())?;
let result = run();
prlimit(previous.trim())?;
Ok(result)
}
#[test]
#[allow(
clippy::result_large_err,
reason = "allocation refusal must return the owned table without allocating a box"
)]
fn allocation_refusals_preserve_ownership_and_exact_storage_stage() -> Result<(), String> {
let case = match env::var(CHILD_CASE) {
Ok(case) => case,
Err(env::VarError::NotPresent) => {
let executable = env::current_exe().map_err(|error| error.to_string())?;
let name = concat!(
module_path!(),
"::allocation_refusals_preserve_ownership_and_exact_storage_stage"
)
.strip_prefix("pipecircuit::")
.ok_or("unexpected test crate")?;
for case in [
"incarnations",
"history",
"selection",
"projected",
"delivery",
] {
let output = Command::new(&executable)
.args(["--exact", name, "--nocapture"])
.env(CHILD_CASE, case)
.env("MALLOC_ARENA_MAX", "1")
.env("MALLOC_MMAP_THRESHOLD_", "65536")
.env("MALLOC_TRIM_THRESHOLD_", "65536")
.output()
.map_err(|error| error.to_string())?;
if !output.status.success() {
return Err(std::format!(
"{case}: {}\n{}\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
}
return Ok(());
}
Err(env::VarError::NotUnicode(_)) => return Err("invalid allocation case".into()),
};
let capacity = Capacity::new(NonZeroU32::new(ROWS).ok_or("zero rows")?);
let rows = usize::try_from(ROWS).map_err(|error| error.to_string())?;
let source_id = SourceId::new(NonZeroU64::MIN);
let view_id = ViewId::new(NonZeroU64::MIN);
match case.as_str() {
"incarnations" | "history" | "selection" => {
let table =
WarpTable::<Actor>::try_new(capacity).map_err(|error| std::format!("{error:?}"))?;
let prefix = match case.as_str() {
"incarnations" => 0,
"history" => size_of::<u64>(),
"selection" => {
size_of::<u64>().saturating_add(size_of::<super::super::source::Change<u32>>())
}
_ => return Err("unknown reserve stage".into()),
};
let result = limited(rows.saturating_mul(prefix), || table.observe(source_id))?;
let Err((table, ObservationCreationError::StorageUnavailable(_))) = result else {
return Err(std::format!("{case}: expected storage refusal"));
};
assert_eq!(table.capacity(), capacity);
assert!(table.is_empty());
}
"projected" => {
let result = limited(0, || {
ViewReplica::<u32, u32>::try_new(source_id, view_id, capacity)
})?;
assert!(matches!(
result,
Err(ViewCreationError::TableUnavailable(
super::super::super::WarpTableCreationError::ColumnsUnavailable {
source: ViewColumnStorageError {
column: "incarnation",
..
}
}
))
));
}
"delivery" => {
let source = source(ROWS).map_err(String::from)?;
let view = view(&source).map_err(String::from)?;
let result = limited(0, || view.delivery_buffer())?;
assert!(matches!(
result,
Err(ViewCreationError::TableUnavailable(
super::super::super::WarpTableCreationError::ColumnsUnavailable {
source: ViewColumnStorageError {
column: "index",
..
}
}
))
));
}
_ => return Err("unknown allocation case".into()),
}
Ok(())
}