Publish release-5edbadb22804 filtered source

Private source: 5edbadb2280419cf2cb42b5237835304ad7040e0

Public tree identity: sha256:e0d4d5e1c8693959e69448684a71582edfc13e9b18d7f1e733034fea5ce62cda
This commit is contained in:
Clusterflux Release 2026-07-29 18:13:15 +02:00
parent b93aef4f25
commit a9f6f8b7a9
12 changed files with 430 additions and 166 deletions

View file

@ -9,6 +9,26 @@ struct LiveLogChunk {
truncated: bool,
}
fn append_bounded_tail(tail: &mut Vec<u8>, bytes: &[u8], maximum: usize) {
if maximum == 0 {
tail.clear();
return;
}
if bytes.len() >= maximum {
tail.clear();
tail.extend_from_slice(&bytes[bytes.len() - maximum..]);
return;
}
let overflow = tail
.len()
.saturating_add(bytes.len())
.saturating_sub(maximum);
if overflow > 0 {
tail.drain(..overflow);
}
tail.extend_from_slice(bytes);
}
fn redact_safe_live_log_prefix(
pending: &[u8],
configured_secrets: &[String],
@ -279,9 +299,9 @@ impl CoordinatorControlledProcessRunner {
let mut captured = Vec::new();
let mut buffer = [0_u8; 16 * 1024];
let stream_base = source_bytes_total.load(Ordering::Relaxed);
let mut source_bytes_read = 0_u64;
let mut pending_offset = stream_base;
let mut pending = Vec::new();
let mut discarded = false;
loop {
let count = reader
.read(&mut buffer)
@ -294,27 +314,21 @@ impl CoordinatorControlledProcessRunner {
Ordering::Relaxed,
|current| Some(current.saturating_add(count as u64)),
);
let remaining = maximum.saturating_sub(captured.len());
let retained = count.min(remaining);
if retained > 0 {
captured.extend_from_slice(&buffer[..retained]);
pending.extend_from_slice(&buffer[..retained]);
if let Some((consumed, text)) =
redact_safe_live_log_prefix(&pending, &configured_secrets, false)
{
let _ = sender.try_send(LiveLogChunk {
stream,
offset: pending_offset,
source_bytes: consumed as u64,
bytes: text.into_bytes(),
truncated: false,
});
pending.drain(..consumed);
pending_offset = pending_offset.saturating_add(consumed as u64);
}
}
if retained < count {
discarded = true;
source_bytes_read = source_bytes_read.saturating_add(count as u64);
append_bounded_tail(&mut captured, &buffer[..count], maximum);
pending.extend_from_slice(&buffer[..count]);
if let Some((consumed, text)) =
redact_safe_live_log_prefix(&pending, &configured_secrets, false)
{
let _ = sender.try_send(LiveLogChunk {
stream,
offset: pending_offset,
source_bytes: consumed as u64,
bytes: text.into_bytes(),
truncated: false,
});
pending.drain(..consumed);
pending_offset = pending_offset.saturating_add(consumed as u64);
}
}
if let Some((consumed, text)) =
@ -328,10 +342,10 @@ impl CoordinatorControlledProcessRunner {
truncated: false,
});
}
if discarded {
if source_bytes_read > maximum as u64 {
let _ = sender.try_send(LiveLogChunk {
stream,
offset: stream_base.saturating_add(maximum as u64),
offset: stream_base.saturating_add(source_bytes_read),
source_bytes: 0,
bytes: b"[log output truncated at node capture limit]".to_vec(),
truncated: true,
@ -616,11 +630,11 @@ mod tests {
}
#[test]
fn bounded_capture_retains_the_complete_source_byte_count() {
fn bounded_capture_retains_the_real_tail_and_complete_source_byte_count() {
let source_bytes = Arc::new(AtomicU64::new(5));
let (sender, receiver) = mpsc::sync_channel(8);
let reader = CoordinatorControlledProcessRunner::drain_bounded(
Cursor::new(vec![b'x'; 32]),
Cursor::new(b"0123456789abcdefghijklmnopqrstuv".to_vec()),
8,
"stdout",
sender,
@ -630,14 +644,14 @@ mod tests {
let captured = reader.join().unwrap().unwrap();
let chunks = receiver.into_iter().collect::<Vec<_>>();
assert_eq!(captured, vec![b'x'; 8]);
assert_eq!(captured, b"opqrstuv");
assert_eq!(source_bytes.load(Ordering::Relaxed), 37);
assert_eq!(
chunks.iter().map(|chunk| chunk.source_bytes).sum::<u64>(),
8
32
);
assert_eq!(chunks[0].offset, 5);
assert_eq!(chunks.last().unwrap().offset, 13);
assert_eq!(chunks.last().unwrap().offset, 37);
assert!(chunks.iter().any(|chunk| chunk.truncated));
}
}

View file

@ -1,6 +1,6 @@
use clusterflux_core::{
CommandInvocation, Digest, LogBuffer, NativeCommandPolicy, NodeId, TaskInstanceId, VfsObject,
VfsOverlay, VfsPath,
CommandInvocation, Digest, NativeCommandPolicy, NodeId, TaskInstanceId, VfsObject, VfsOverlay,
VfsPath,
};
use serde::{Deserialize, Serialize};
@ -113,24 +113,20 @@ impl LocalCommandExecutor {
}
pub(super) fn capture_command_logs(
task: &TaskInstanceId,
_task: &TaskInstanceId,
stdout: &[u8],
stderr: &[u8],
max_log_bytes: usize,
) -> CapturedCommandLogs {
let mut logs = LogBuffer::new(max_log_bytes);
logs.push(task.clone(), stdout);
logs.push(task.clone(), stderr);
let records = logs.records();
let stdout_record = &records[0];
let stderr_record = &records[1];
debug_assert_eq!(&stdout_record.task, task);
debug_assert_eq!(&stderr_record.task, task);
let stdout_truncated = stdout.len() > max_log_bytes;
let stderr_truncated = stderr.len() > max_log_bytes;
let stdout_start = stdout.len().saturating_sub(max_log_bytes);
let stderr_start = stderr.len().saturating_sub(max_log_bytes);
CapturedCommandLogs {
stdout: String::from_utf8_lossy(&stdout_record.bytes).into_owned(),
stderr: String::from_utf8_lossy(&stderr_record.bytes).into_owned(),
stdout_truncated: stdout_record.truncated,
stderr_truncated: stderr_record.truncated,
backpressured: logs.backpressured(),
stdout: String::from_utf8_lossy(&stdout[stdout_start..]).into_owned(),
stderr: String::from_utf8_lossy(&stderr[stderr_start..]).into_owned(),
stdout_truncated,
stderr_truncated,
backpressured: stdout_truncated || stderr_truncated,
}
}

View file

@ -741,7 +741,7 @@ mod tests {
}
#[test]
fn linux_backend_caps_logs_without_truncating_staged_artifact_bytes() {
fn linux_backend_retains_final_log_tail_without_truncating_staged_artifact_bytes() {
let invocation = CommandInvocation {
program: "cargo".to_owned(),
args: vec!["build".to_owned()],
@ -774,7 +774,7 @@ mod tests {
.unwrap();
assert_eq!(output.virtual_thread, TaskInstanceId::from("compile-linux"));
assert_eq!(output.stdout, "abcd");
assert_eq!(output.stdout, "cdef");
assert!(output.stdout_truncated);
assert!(output.log_backpressured);
assert_eq!(output.staged_artifact.as_ref().unwrap().size, 6);
@ -992,7 +992,7 @@ mod tests {
#[cfg(unix)]
#[test]
fn local_command_executor_caps_logs_and_reports_backpressure_by_virtual_thread() {
fn local_command_executor_retains_each_stream_tail_and_reports_backpressure() {
let executor = LocalCommandExecutor {
node: clusterflux_core::NodeId::from("node"),
hosted_control_plane: false,
@ -1021,10 +1021,10 @@ mod tests {
.unwrap();
assert_eq!(output.virtual_thread, TaskInstanceId::from("compile-linux"));
assert_eq!(output.stdout, "abcd");
assert_eq!(output.stderr, "");
assert_eq!(output.stdout, "cdef");
assert_eq!(output.stderr, "err");
assert!(output.stdout_truncated);
assert!(output.stderr_truncated);
assert!(!output.stderr_truncated);
assert!(output.log_backpressured);
}