Skip to content

Commit 1ddc4c8

Browse files
committed
Add process timeout runtime hooks
1 parent c0d34c9 commit 1ddc4c8

3 files changed

Lines changed: 119 additions & 9 deletions

File tree

core/process/process.rssi

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,26 @@ pub native fn Process.run_stdout(
66
) -> Result<String, String>
77
effects(native)
88

9+
pub native fn Process.run_stdout_timeout(
10+
command: read String,
11+
args: read List<String>,
12+
timeout_ms: Int,
13+
) -> Result<String, String>
14+
effects(native)
15+
916
pub native fn Process.run_many_stdout(
1017
command: read String,
1118
args: read List<String>,
1219
appended_args: read List<String>,
1320
jobs: Int,
1421
) -> Result<List<String>, String>
1522
effects(native)
23+
24+
pub native fn Process.run_many_stdout_timeout(
25+
command: read String,
26+
args: read List<String>,
27+
appended_args: read List<String>,
28+
jobs: Int,
29+
timeout_ms: Int,
30+
) -> Result<List<String>, String>
31+
effects(native)

runtime/src/lib.rs

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,34 +1013,117 @@ pub fn process_run_stdout(command: &str, args: &[String]) -> Result<String, Stri
10131013
let output = child
10141014
.output()
10151015
.map_err(|error| format!("failed to run `{command}`: {error}"))?;
1016+
process_output_result(command, output)
1017+
}
1018+
1019+
pub fn process_run_stdout_timeout(
1020+
command: &str,
1021+
args: &[String],
1022+
timeout_ms: i64,
1023+
) -> Result<String, String> {
1024+
if timeout_ms <= 0 {
1025+
return process_run_stdout(command, args);
1026+
}
1027+
1028+
let timeout_ms = u64::try_from(timeout_ms).unwrap_or(0);
1029+
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
1030+
let mut child = std::process::Command::new(command);
1031+
child
1032+
.args(args)
1033+
.stdout(std::process::Stdio::piped())
1034+
.stderr(std::process::Stdio::piped());
1035+
if std::env::var_os("RSSCRIPT_RAMDISK_PATH").is_none()
1036+
&& let Some(path) = default_ramdisk_root_dir()
1037+
{
1038+
child.env("RSSCRIPT_RAMDISK_PATH", path);
1039+
}
1040+
let mut child = child
1041+
.spawn()
1042+
.map_err(|error| format!("failed to run `{command}`: {error}"))?;
1043+
loop {
1044+
if child
1045+
.try_wait()
1046+
.map_err(|error| format!("failed to poll `{command}`: {error}"))?
1047+
.is_some()
1048+
{
1049+
let output = child
1050+
.wait_with_output()
1051+
.map_err(|error| format!("failed to collect `{command}` output: {error}"))?;
1052+
return process_output_result(command, output);
1053+
}
1054+
let now = Instant::now();
1055+
if now >= deadline {
1056+
let _ = child.kill();
1057+
let output = child
1058+
.wait_with_output()
1059+
.map_err(|error| format!("failed to collect `{command}` output: {error}"))?;
1060+
return Err(format!(
1061+
"`{command}` timed out after {timeout_ms}ms: {}",
1062+
process_output_details(&output)
1063+
));
1064+
}
1065+
std::thread::sleep((deadline - now).min(Duration::from_millis(10)));
1066+
}
1067+
}
1068+
1069+
fn process_output_result(command: &str, output: std::process::Output) -> Result<String, String> {
10161070
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
10171071
if output.status.success() {
10181072
return Ok(stdout);
10191073
}
10201074

1075+
let code = output
1076+
.status
1077+
.code()
1078+
.map(|code| code.to_string())
1079+
.unwrap_or_else(|| "signal".to_string());
1080+
Err(format!(
1081+
"`{command}` exited with {code}: {}",
1082+
process_output_details(&output)
1083+
))
1084+
}
1085+
1086+
fn process_output_details(output: &std::process::Output) -> String {
1087+
let stdout = String::from_utf8_lossy(&output.stdout);
10211088
let stderr = String::from_utf8_lossy(&output.stderr);
10221089
let stdout = stdout.trim();
10231090
let stderr = stderr.trim();
1024-
let details = if stdout.is_empty() {
1091+
if stdout.is_empty() {
10251092
stderr.to_string()
10261093
} else if stderr.is_empty() {
10271094
stdout.to_string()
10281095
} else {
10291096
format!("{stdout}\n{stderr}")
1030-
};
1031-
let code = output
1032-
.status
1033-
.code()
1034-
.map(|code| code.to_string())
1035-
.unwrap_or_else(|| "signal".to_string());
1036-
Err(format!("`{command}` exited with {code}: {details}"))
1097+
}
10371098
}
10381099

10391100
pub fn process_run_many_stdout(
10401101
command: &str,
10411102
args: &[String],
10421103
appended_args: &[String],
10431104
jobs: i64,
1105+
) -> Result<Vec<String>, String> {
1106+
process_run_many_stdout_with_runner(command, args, appended_args, jobs, process_run_stdout)
1107+
}
1108+
1109+
pub fn process_run_many_stdout_timeout(
1110+
command: &str,
1111+
args: &[String],
1112+
appended_args: &[String],
1113+
jobs: i64,
1114+
timeout_ms: i64,
1115+
) -> Result<Vec<String>, String> {
1116+
process_run_many_stdout_with_runner(command, args, appended_args, jobs, |command, args| {
1117+
process_run_stdout_timeout(command, args, timeout_ms)
1118+
})
1119+
}
1120+
1121+
fn process_run_many_stdout_with_runner(
1122+
command: &str,
1123+
args: &[String],
1124+
appended_args: &[String],
1125+
jobs: i64,
1126+
runner: impl Fn(&str, &[String]) -> Result<String, String> + Send + Sync,
10441127
) -> Result<Vec<String>, String> {
10451128
if appended_args.is_empty() {
10461129
return Ok(Vec::new());
@@ -1056,6 +1139,7 @@ pub fn process_run_many_stdout(
10561139
let errors = std::sync::Arc::new(std::sync::Mutex::new(Vec::<String>::new()));
10571140
let args = std::sync::Arc::new(args.to_vec());
10581141
let appended_args = std::sync::Arc::new(appended_args.to_vec());
1142+
let runner = &runner;
10591143

10601144
std::thread::scope(|scope| {
10611145
for _ in 0..worker_count {
@@ -1072,7 +1156,7 @@ pub fn process_run_many_stdout(
10721156
};
10731157
let mut command_args = (*args).clone();
10741158
command_args.push(appended_arg.clone());
1075-
match process_run_stdout(command, &command_args) {
1159+
match runner(command, &command_args) {
10761160
Ok(stdout) => {
10771161
if let Ok(mut result) = results[index].lock() {
10781162
*result = Some(stdout);

src/runtime_abi.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,21 @@ const RUNTIME_INTRINSICS: &[RuntimeIntrinsic] = &[
312312
"run_stdout",
313313
"rsscript_runtime::process_run_stdout",
314314
),
315+
runtime_intrinsic(
316+
"Process",
317+
"run_stdout_timeout",
318+
"rsscript_runtime::process_run_stdout_timeout",
319+
),
315320
runtime_intrinsic(
316321
"Process",
317322
"run_many_stdout",
318323
"rsscript_runtime::process_run_many_stdout",
319324
),
325+
runtime_intrinsic(
326+
"Process",
327+
"run_many_stdout_timeout",
328+
"rsscript_runtime::process_run_many_stdout_timeout",
329+
),
320330
runtime_intrinsic("Request", "new", "rsscript_runtime::request_new"),
321331
runtime_intrinsic("Request", "path", "rsscript_runtime::request_path"),
322332
runtime_intrinsic("Response", "body", "rsscript_runtime::response_body"),

0 commit comments

Comments
 (0)