-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow limiting system resources in compilation processes
- Loading branch information
1 parent
9eeac6f
commit 327a1f1
Showing
6 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use std::io; | ||
use std::os::unix::process::CommandExt; | ||
use std::process::Command; | ||
|
||
use rlimit::{setrlimit, Resource}; | ||
|
||
#[cfg(test)] | ||
#[path = "resource_limits_test.rs"] | ||
pub mod test; | ||
|
||
#[allow(dead_code)] | ||
struct ResourceLimits { | ||
resource: Resource, | ||
soft: u64, | ||
hard: u64, | ||
units: String, | ||
} | ||
|
||
impl ResourceLimits { | ||
#[allow(dead_code)] | ||
fn set(&self) -> io::Result<()> { | ||
// Use `println!` and not a logger because this method is called in an unsafe block, and we | ||
// don't want to risk unexpected behavior. | ||
println!("Setting {:?} limits to {} {}.", self.resource, self.soft, self.units); | ||
setrlimit(self.resource, self.soft, self.hard) | ||
} | ||
} | ||
|
||
#[allow(dead_code)] | ||
struct ResourcesLimits { | ||
cpu_time: Option<ResourceLimits>, | ||
file_size: Option<ResourceLimits>, | ||
memory_size: Option<ResourceLimits>, | ||
} | ||
|
||
impl ResourcesLimits { | ||
#[allow(dead_code)] | ||
fn new( | ||
cpu_time: Option<u64>, | ||
file_size: Option<u64>, | ||
memory_size: Option<u64>, | ||
) -> ResourcesLimits { | ||
ResourcesLimits { | ||
cpu_time: cpu_time.map(|x| ResourceLimits { | ||
resource: Resource::CPU, | ||
soft: x, | ||
hard: x, | ||
units: "seconds".to_string(), | ||
}), | ||
file_size: file_size.map(|x| ResourceLimits { | ||
resource: Resource::FSIZE, | ||
soft: x, | ||
hard: x, | ||
units: "bytes".to_string(), | ||
}), | ||
memory_size: memory_size.map(|x| ResourceLimits { | ||
resource: Resource::AS, | ||
soft: x, | ||
hard: x, | ||
units: "bytes".to_string(), | ||
}), | ||
} | ||
} | ||
|
||
#[allow(dead_code)] | ||
fn set(&self) -> io::Result<()> { | ||
[self.cpu_time.as_ref(), self.file_size.as_ref(), self.memory_size.as_ref()] | ||
.iter() | ||
.flatten() | ||
.try_for_each(|resource_limit| resource_limit.set()) | ||
} | ||
|
||
#[allow(dead_code)] | ||
fn apply(self, command: &mut Command) -> &mut Command { | ||
#[cfg(unix)] | ||
unsafe { | ||
// The `pre_exec` method runs a given closure after the parent process has been forked | ||
// but before the child process calls `exec`. | ||
// | ||
// This closure runs in the child process after a `fork`, which primarily means that any | ||
// modifications made to memory on behalf of this closure will **not** be visible to the | ||
// parent process. This environment is often very constrained. Normal operations--such | ||
// as using `malloc`, accessing environment variables through [`std::env`] or acquiring | ||
// a mutex--are not guaranteed to work, because other threads may still be running at | ||
// the time of `fork`. | ||
// | ||
// This closure is considered safe for the following reasons: | ||
// 1. The [`ResourcesLimits`] struct is fully constructed and moved into the closure. | ||
// 2. No heap allocations occur in the `set` method. | ||
// 3. `setrlimit` is an async-signal-safe system call, which means it is safe to invoke | ||
// after `fork`. | ||
command.pre_exec(move || self.set()) | ||
} | ||
#[cfg(not(unix))] | ||
// Not implemented for Windows. | ||
unimplemented!("Resource limits are not implemented for Windows.") | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
crates/starknet_sierra_compile/src/resource_limits_test.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
use std::process::Command; | ||
use std::time::Instant; | ||
|
||
use rstest::rstest; | ||
|
||
use crate::resource_limits::ResourcesLimits; | ||
|
||
#[rstest] | ||
fn test_cpu_time_limit() { | ||
let cpu_limit = 1; // 1 second | ||
let cpu_time_rlimit = ResourcesLimits::new(Some(1), None, None); | ||
|
||
let start = Instant::now(); | ||
let mut command = Command::new("bash"); | ||
command.args(["-c", "while true; do :; done;"]); | ||
cpu_time_rlimit.apply(&mut command); | ||
command.spawn().expect("Failed to start CPU consuming process").wait().unwrap(); | ||
assert!(start.elapsed().as_secs() <= 1); | ||
} | ||
|
||
#[rstest] | ||
fn test_memory_size_limit() { | ||
let memory_limit = 100 * 1024; // 100 KB | ||
let memory_size_rlimit = ResourcesLimits::new(None, None, Some(memory_limit)); | ||
|
||
let mut command = Command::new("bash"); | ||
command.args(["-c", "a=(); while true; do a+=0; done;"]); | ||
memory_size_rlimit.apply(&mut command); | ||
command.spawn().expect("Failed to start memory consuming process").wait().unwrap(); | ||
} | ||
|
||
#[rstest] | ||
fn test_file_size_limit() { | ||
let file_limit = 10; // 10 bytes | ||
let file_size_rlimit = ResourcesLimits::new(None, Some(file_limit), None); | ||
|
||
let mut command = Command::new("bash"); | ||
command.args(["-c", "echo 0 > /tmp/file.txt; while true; do echo 0 >> /tmp/file.txt; done;"]); | ||
file_size_rlimit.apply(&mut command); | ||
command.spawn().expect("Failed to start disk consuming process").wait().unwrap(); | ||
assert!(std::fs::metadata("/tmp/file.txt").unwrap().len() <= file_limit); | ||
std::fs::remove_file("/tmp/file.txt").unwrap(); | ||
} |