aboutsummaryrefslogtreecommitdiffstats
path: root/crates/asist-core/src/proc.rs
diff options
context:
space:
mode:
authorelvis <elvis@claros.ar>2026-09-26 20:20:19 -0300
committerelvis <elvis@claros.ar>2026-09-26 20:20:19 -0300
commit8518a63f55153e7f45fd49ad6caff5555f4e374f (patch)
tree636684eea3fa6f35d78282ab95eb687ef49154af /crates/asist-core/src/proc.rs
parent69de76dc9cbedc6092d1e5ce84094a8030de1470 (diff)
downloadasist-p-8518a63f55153e7f45fd49ad6caff5555f4e374f.tar.gz
asist-p-8518a63f55153e7f45fd49ad6caff5555f4e374f.zip
Translate code, comments, logs and terminal UI to English; add English README; rename scriptsHEADmain
Diffstat (limited to 'crates/asist-core/src/proc.rs')
-rw-r--r--crates/asist-core/src/proc.rs80
1 files changed, 41 insertions, 39 deletions
diff --git a/crates/asist-core/src/proc.rs b/crates/asist-core/src/proc.rs
index 8e38108..976a2a8 100644
--- a/crates/asist-core/src/proc.rs
+++ b/crates/asist-core/src/proc.rs
@@ -1,12 +1,15 @@
-//! Ejecutar un programa con un plazo máximo.
+//! Running a program with a deadline.
//!
-//! Tres sitios lo necesitaban con los mismos cuidados —no dejar nunca un
-//! proceso colgado, no pasar por una shell, distinguir «falló» de «tardó
-//! demasiado»— y cada uno lo tenía escrito a su manera. Aquí está una sola vez.
+//! Three places needed it with the same care (never leave a process hanging,
+//! never go through a shell, tell «failed» from «took too long») and each had
+//! its own version. Here it is, once.
//!
-//! Nada de esto pasa por `sh`: los argumentos van al `execve` tal cual. Importa
-//! más de lo que parece, porque lo que acaba en ellos viene, en última
-//! instancia, de lo que se ha oído por el micrófono.
+//! None of this goes through `sh`: the arguments go to `execve` as they are.
+//! It matters more than it seems, because what ends up in them comes,
+//! ultimately, from what the microphone heard.
+//!
+//! The error messages are in Spanish on purpose: they end up in tool results
+//! that the model reads and speaks.
use std::ffi::OsStr;
use std::io::Read;
@@ -14,7 +17,7 @@ use std::path::Path;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
-/// Cómo terminó.
+/// How it ended.
#[derive(Debug)]
pub struct Output {
pub stdout: Vec<u8>,
@@ -28,8 +31,8 @@ impl Output {
self.status == Some(0)
}
- /// Última línea del error, que es donde los programas suelen poner el
- /// motivo de verdad.
+ /// Last line of stderr, which is where programs usually put the real
+ /// reason.
pub fn last_error_line(&self) -> &str {
self.stderr
.trim()
@@ -57,11 +60,11 @@ pub enum ProcError {
},
}
-/// Lanza `program`, espera hasta `timeout` y devuelve lo que haya escrito.
+/// Launches `program`, waits up to `timeout` and returns whatever it wrote.
///
-/// Si vence el plazo, mata el proceso antes de rendirse: un ffmpeg contra una
-/// cámara ocupada, o un buscador que no contesta, se quedarían ahí para
-/// siempre.
+/// If the deadline passes, it kills the process before giving up: an ffmpeg
+/// against a busy camera, or a search engine that does not answer, would stay
+/// there forever.
pub fn run(
program: impl AsRef<Path>,
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
@@ -87,14 +90,13 @@ pub fn run(
source,
})?;
- // Las tuberías se vacían en hilos aparte, y no después de esperar.
+ // The pipes are drained on separate threads, not after waiting.
//
- // Esto no es una optimización: un hijo que escribe más de lo que cabe en el
- // búfer de la tubería (64 KB en Linux) se queda bloqueado escribiendo, no
- // termina nunca, y el bucle de espera acaba matándolo por plazo vencido
- // aunque estuviera haciendo su trabajo. Costó descubrirlo porque los tres
- // primeros usos —una fecha, un JSON de búsqueda, un fotograma de 9 KB—
- // cabían de sobra; la primera captura de pantalla, de 180 KB, no.
+ // This is not an optimization: a child that writes more than fits in the
+ // pipe buffer (64 KB on Linux) blocks writing, never finishes, and the wait
+ // loop ends up killing it for missing the deadline even though it was doing
+ // its job. It was hard to find because the first three uses (a date, a
+ // search JSON, a 9 KB frame) fit easily; the first 180 KB screenshot did not.
let mut stdout = child.stdout.take();
let mut stderr = child.stderr.take();
let stdout_reader = std::thread::spawn(move || {
@@ -117,8 +119,8 @@ pub fn run(
match child.try_wait() {
Ok(Some(status)) => break status,
Ok(None) if Instant::now() >= deadline => {
- // Matar cierra las tuberías, así que los lectores terminan y
- // se pueden recoger sin quedarse colgados.
+ // Killing closes the pipes, so the readers finish and can be
+ // joined without hanging.
let _ = child.kill();
let _ = child.wait();
let _ = stdout_reader.join();
@@ -154,75 +156,75 @@ mod tests {
use super::*;
#[test]
- fn se_recoge_la_salida_estandar() {
+ fn stdout_is_collected() {
let out = run("echo", ["hola"], Duration::from_secs(5), None).unwrap();
assert!(out.success());
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "hola");
}
#[test]
- fn un_proceso_colgado_se_mata_al_vencer_el_plazo() {
+ fn hung_process_is_killed_at_the_deadline() {
let started = Instant::now();
let err = run("sleep", ["30"], Duration::from_millis(300), None).unwrap_err();
assert!(matches!(err, ProcError::Timeout { .. }), "{err}");
assert!(
started.elapsed() < Duration::from_secs(3),
- "debió cortar enseguida, tardó {:?}",
+ "should have stopped right away, took {:?}",
started.elapsed()
);
}
#[test]
- fn una_salida_grande_no_bloquea_al_hijo() {
- // La regresión que dejó colgada la captura de pantalla: sin vaciar la
- // tubería mientras se espera, un hijo que escribe más de 64 KB se
- // bloquea y acaba muriendo por plazo vencido.
+ fn large_output_does_not_block_the_child() {
+ // The regression that hung the screenshot: without draining the pipe
+ // while waiting, a child that writes more than 64 KB blocks and ends up
+ // dying for missing the deadline.
let out = run(
"dd",
["if=/dev/zero", "bs=1024", "count=512", "status=none"],
Duration::from_secs(10),
None,
)
- .expect("no debió vencer el plazo");
+ .expect("the deadline should not have passed");
assert!(out.success());
assert_eq!(out.stdout.len(), 512 * 1024);
}
#[test]
- fn el_error_grande_tampoco_bloquea() {
- // Mismo problema por la otra tubería: mucho ruido en stderr y poca
- // salida es justo lo que hace ffmpeg cuando algo va mal.
+ fn large_stderr_does_not_block_either() {
+ // Same problem on the other pipe: lots of noise on stderr and little
+ // output is exactly what ffmpeg does when something goes wrong.
let out = run(
"sh",
["-c", "yes error | head -c 200000 >&2; echo ok"],
Duration::from_secs(10),
None,
)
- .expect("no debió vencer el plazo");
+ .expect("the deadline should not have passed");
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "ok");
assert!(out.stderr.len() > 100_000);
}
#[test]
- fn un_programa_inexistente_da_un_error_con_su_nombre() {
+ fn missing_program_error_includes_its_name() {
let err = run("/no/existe/nada", ["x"], Duration::from_secs(1), None).unwrap_err();
assert!(err.to_string().contains("/no/existe/nada"), "{err}");
}
#[test]
- fn un_fallo_conserva_el_codigo_y_el_mensaje() {
+ fn failure_keeps_the_code_and_message() {
let out = run("ls", ["/no/existe"], Duration::from_secs(5), None).unwrap();
assert!(!out.success());
assert!(!out.last_error_line().is_empty());
}
#[test]
- fn los_metacaracteres_no_los_interpreta_ninguna_shell() {
+ fn no_shell_interprets_metacharacters() {
let out = run("echo", ["a; echo b"], Duration::from_secs(5), None).unwrap();
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"a; echo b",
- "el punto y coma debe llegar como texto, no como separador de órdenes"
+ "the semicolon must arrive as text, not as a command separator"
);
}
}