Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't read stdout into memory when not needed #143

Merged
merged 28 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
49b6946
[BROKEN] Add memory test for not consuming stdout
soenkehahn Aug 4, 2021
469a187
!fixup
soenkehahn Aug 4, 2021
a06ff81
Fix memory-test for rustc <= 1.44
soenkehahn Aug 4, 2021
8fb2165
[CI] Make memory test pass temporarily
soenkehahn Aug 4, 2021
05c046f
Fix clippy warnings
soenkehahn Aug 4, 2021
3e6e555
Fix clippy warnings for real
soenkehahn Aug 4, 2021
d390b04
Only run memory-test on linux on ci
soenkehahn Aug 4, 2021
a7ca759
Put real memory limit back in place
soenkehahn Aug 5, 2021
e8cbb49
Save stdout as Option internally to avoid masking bugs
soenkehahn Aug 6, 2021
5455ffc
Tweaks
soenkehahn Aug 7, 2021
6dca175
Integrate memory-test with cargo test suite
soenkehahn Aug 7, 2021
8cf5664
Use `mib` for mebibyte
soenkehahn Aug 8, 2021
73b36da
kibibytes
soenkehahn Aug 8, 2021
1449f40
Tweak error message
soenkehahn Aug 8, 2021
16d9402
Tweak error message
soenkehahn Aug 8, 2021
0e5b5ab
Fix compilation
soenkehahn Aug 8, 2021
f4ee803
Rename CradleBug -> Internal
soenkehahn Aug 8, 2021
8e5e289
KiB
soenkehahn Aug 8, 2021
789536e
KiB
soenkehahn Aug 8, 2021
4c3cf4b
Simplify produce_bytes
soenkehahn Aug 8, 2021
13dd24e
Fix produce_bytes
soenkehahn Aug 8, 2021
92a1a13
Refactor relay_stdout to capture_stdout
soenkehahn Aug 8, 2021
9e5334d
Rename relay_stderr to capture_stderr for consistency
soenkehahn Aug 8, 2021
a5a0ae6
Merge remote-tracking branch 'origin/master' into optimization
soenkehahn Aug 8, 2021
32f1fff
Do something
soenkehahn Aug 9, 2021
478c938
Disable memory test for non-Linux
soenkehahn Aug 9, 2021
04db67a
[CI] Bump cache key
soenkehahn Aug 9, 2021
b538bfe
[CI] Fixup
soenkehahn Aug 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/target/
Cargo.lock
/Cargo.lock
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ homepage = "https://github.com/soenkehahn/cradle"
keywords = ["child", "child-process", "command", "process", "shell"]
categories = ["filesystem", "os"]

[workspace]
members = [".", "memory-test"]

[dependencies]
rustversion = "1.0.4"

Expand Down
8 changes: 4 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
ci: test build doc clippy fmt context-integration-tests run-examples forbidden-words render-readme-check

build:
cargo build --all-targets --all-features
cargo build --all-targets --all-features --workspace

test +pattern="":
cargo test --all {{ pattern }}
cargo test {{ pattern }}

test-lib-fast +pattern="":
cargo test --lib {{ pattern }}
Expand All @@ -13,10 +13,10 @@ context-integration-tests:
cargo run --features "test_executables" --bin context_integration_tests

doc +args="":
cargo doc --all {{args}}
cargo doc --workspace {{args}}

clippy:
cargo clippy --all-targets --all-features
cargo clippy --all-targets --all-features --workspace

fmt:
cargo fmt --all -- --check
Expand Down
31 changes: 31 additions & 0 deletions memory-test/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions memory-test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "memory-test"
version = "0.0.0"
edition = "2018"

[dependencies]
anyhow = "1.0.42"
cradle = { path = ".." }
rustversion = "1.0.5"
8 changes: 8 additions & 0 deletions memory-test/src/bin/cradle_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use cradle::prelude::*;

fn main() {
let mut args = std::env::args();
let bytes: usize = args.nth(1).unwrap().parse().unwrap();
eprintln!("consuming {} kB", bytes / 2_usize.pow(10));
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
cmd_unit!("./target/release/produce_bytes", bytes.to_string());
}
21 changes: 21 additions & 0 deletions memory-test/src/bin/produce_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use anyhow::Result;
use std::io::{stdout, Write};

fn main() -> Result<()> {
let mut args = std::env::args();
let mut bytes: usize = args.nth(1).unwrap().parse()?;
eprintln!("producing {} kB", bytes / 2_usize.pow(10));
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
let buffer = &[b'x'; 1024];
let mut stdout = stdout();
while bytes > 0 {
if bytes >= buffer.len() {
stdout.write_all(buffer)?;
bytes -= buffer.len();
} else {
stdout.write_all(&[b'x'])?;
bytes -= 1;
}
}
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
stdout.flush()?;
Ok(())
}
56 changes: 56 additions & 0 deletions memory-test/src/bin/run_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use anyhow::Result;
use cradle::prelude::*;
use std::process::{Command, Stdio};

fn from_mb(mega_bytes: usize) -> usize {
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
mega_bytes * 2_usize.pow(20)
}

fn main() -> Result<()> {
Split("cargo build --release").run_unit();
let bytes = from_mb(64);
let memory_consumption = measure_memory_consumption(bytes)?;
let allowed_memory_consumption = from_mb(16);
assert!(
memory_consumption < allowed_memory_consumption,
"Maximum resident set size: {}, allowed upper limit: {}",
memory_consumption,
allowed_memory_consumption
);
Ok(())
}

fn measure_memory_consumption(bytes: usize) -> Result<usize> {
let output = Command::new("/usr/bin/time")
.arg("-v")
.arg("./target/release/cradle_user")
.arg(bytes.to_string())
.stdout(Stdio::null())
.output()?;
let stderr = String::from_utf8(output.stderr)?;
eprintln!("{}", stderr);
if !output.status.success() {
panic!("running 'cradle_user' failed");
}
let memory_size_prefix = "Maximum resident set size (kbytes): ";
let kilo_bytes: usize = strip_prefix(
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
stderr
.lines()
.map(|line| line.trim())
.find(|line| line.starts_with(memory_size_prefix))
.unwrap(),
memory_size_prefix,
)
.parse()?;
let bytes = kilo_bytes * 1024;
Ok(bytes)
}

#[rustversion::attr(since(1.48), allow(clippy::manual_strip))]
fn strip_prefix<'a>(string: &'a str, prefix: &'a str) -> &'a str {
if string.starts_with(prefix) {
&string[prefix.len()..]
} else {
panic!("{} doesn't start with {}", string, prefix);
}
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 7 additions & 5 deletions src/collected_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{

pub(crate) struct Waiter {
stdin: JoinHandle<io::Result<()>>,
stdout: JoinHandle<io::Result<Vec<u8>>>,
stdout: JoinHandle<io::Result<Option<Vec<u8>>>>,
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
stderr: JoinHandle<io::Result<Vec<u8>>>,
}

Expand All @@ -30,8 +30,8 @@ impl Waiter {
});
let mut context_clone = context.clone();
let relay_stdout = config.relay_stdout;
let stdout_join_handle = thread::spawn(move || -> io::Result<Vec<u8>> {
let mut collected_stdout = Vec::new();
let stdout_join_handle = thread::spawn(move || -> io::Result<Option<Vec<u8>>> {
let mut collected_stdout = if relay_stdout { None } else { Some(Vec::new()) };
casey marked this conversation as resolved.
Show resolved Hide resolved
let buffer = &mut [0; 256];
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
loop {
let length = child_stdout.read(buffer)?;
Expand All @@ -41,7 +41,9 @@ impl Waiter {
if relay_stdout {
context_clone.stdout.write_all(&buffer[..length])?;
}
collected_stdout.extend(&buffer[..length]);
if let Some(collected_stdout) = &mut collected_stdout {
collected_stdout.extend(&buffer[..length]);
}
}
Ok(collected_stdout)
});
Expand Down Expand Up @@ -87,6 +89,6 @@ impl Waiter {
}

pub(crate) struct CollectedOutput {
pub(crate) stdout: Vec<u8>,
pub(crate) stdout: Option<Vec<u8>>,
pub(crate) stderr: Vec<u8>,
}
1 change: 1 addition & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc};

#[doc(hidden)]
#[rustversion::attr(since(1.48), allow(clippy::rc_buffer))]
#[derive(Debug, Clone)]
pub struct Config {
pub(crate) arguments: Vec<OsString>,
pub(crate) log_command: bool,
Expand Down
45 changes: 34 additions & 11 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub enum Error {
full_command: String,
source: Arc<FromUtf8Error>,
},
CradleBug {
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
full_command: String,
config: Config,
},
}

impl Error {
Expand All @@ -35,6 +39,13 @@ impl Error {
source: Arc::new(error),
}
}

pub(crate) fn cradle_bug(config: &Config) -> Error {
Error::CradleBug {
full_command: config.full_command(),
config: config.clone(),
}
}
}

#[doc(hidden)]
Expand All @@ -48,15 +59,16 @@ pub fn panic_on_error<T>(result: Result<T, Error>) -> T {

impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Error::*;
match self {
Error::NoArgumentsGiven => write!(f, "no arguments given"),
Error::FileNotFoundWhenExecuting { executable, .. } => write!(
NoArgumentsGiven => write!(f, "no arguments given"),
FileNotFoundWhenExecuting { executable, .. } => write!(
f,
"File not found error when executing '{}'",
executable.to_string_lossy()
),
Error::CommandIoError { message, .. } => write!(f, "{}", message),
Error::NonZeroExitCode {
CommandIoError { message, .. } => write!(f, "{}", message),
NonZeroExitCode {
full_command,
exit_status,
} => {
Expand All @@ -70,24 +82,35 @@ impl Display for Error {
write!(f, "{}:\n exited with {}", full_command, exit_status)
}
}
Error::InvalidUtf8ToStdout { full_command, .. } => {
InvalidUtf8ToStdout { full_command, .. } => {
write!(f, "{}:\n invalid utf-8 written to stdout", full_command)
}
Error::InvalidUtf8ToStderr { full_command, .. } => {
InvalidUtf8ToStderr { full_command, .. } => {
write!(f, "{}:\n invalid utf-8 written to stderr", full_command)
}
CradleBug { .. } => {
let snippets = vec![
"Congratulations, you've found a bug in cradle! :/",
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
"Please, consider reporting a bug on https://github.com/soenkehahn/cradle/issues,",
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
"including the following information:",
soenkehahn marked this conversation as resolved.
Show resolved Hide resolved
];
writeln!(f, "{}\n{:#?}", snippets.join(" "), self)
}
}
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match self {
Error::FileNotFoundWhenExecuting { source, .. }
| Error::CommandIoError { source, .. } => Some(&**source),
Error::InvalidUtf8ToStdout { source, .. }
| Error::InvalidUtf8ToStderr { source, .. } => Some(&**source),
Error::NoArgumentsGiven | Error::NonZeroExitCode { .. } => None,
FileNotFoundWhenExecuting { source, .. } | CommandIoError { source, .. } => {
Some(&**source)
}
InvalidUtf8ToStdout { source, .. } | InvalidUtf8ToStderr { source, .. } => {
Some(&**source)
}
NoArgumentsGiven | NonZeroExitCode { .. } | CradleBug { .. } => None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ where
#[doc(hidden)]
#[derive(Clone)]
pub struct RunResult {
stdout: Vec<u8>,
stdout: Option<Vec<u8>>,
stderr: Vec<u8>,
exit_status: ExitStatus,
}
Expand Down
3 changes: 2 additions & 1 deletion src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ impl Output for StdoutUntrimmed {
#[doc(hidden)]
fn from_run_result(config: &Config, result: Result<RunResult, Error>) -> Result<Self, Error> {
let result = result?;
Ok(StdoutUntrimmed(String::from_utf8(result.stdout).map_err(
let stdout = result.stdout.ok_or_else(|| Error::cradle_bug(config))?;
Ok(StdoutUntrimmed(String::from_utf8(stdout).map_err(
|source| Error::InvalidUtf8ToStdout {
full_command: config.full_command(),
source: Arc::new(source),
Expand Down
7 changes: 7 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,10 @@ mod run_interface {
);
}
}

#[test]
fn memory_test() {
use cradle::prelude::*;
cmd_unit!(%"cargo build -p memory-test --release");
cmd_unit!(%"cargo run -p memory-test --bin run_test");
}