mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Make head-tail buffer capacity const generic (#39493)
## What changed - Parameterize `HeadTailBuffer` by a const `MAX_BYTES` value and derive its head and tail budgets from that value. - Keep `UNIFIED_EXEC_OUTPUT_MAX_BYTES` as the default capacity for production call sites. - Update focused buffer tests to select their smaller capacities through the type parameter. GitOrigin-RevId: 106e81a2c2db935f7f77a4f301ef591017958287
This commit is contained in:
committed by
copyberry
parent
d1d51f6315
commit
fcdf2b5014
@@ -6,40 +6,17 @@ use std::collections::VecDeque;
|
||||
/// dropping the middle once it exceeds the configured maximum. The buffer is
|
||||
/// symmetric meaning 50% of the capacity is allocated to the head and 50% is
|
||||
/// allocated to the tail.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||
pub(crate) struct HeadTailBuffer {
|
||||
max_bytes: usize,
|
||||
head_budget: usize,
|
||||
tail_budget: usize,
|
||||
pub(crate) struct HeadTailBuffer<const MAX_BYTES: usize = UNIFIED_EXEC_OUTPUT_MAX_BYTES> {
|
||||
head: Vec<u8>,
|
||||
tail: VecDeque<u8>,
|
||||
omitted_bytes: usize,
|
||||
}
|
||||
|
||||
impl Default for HeadTailBuffer {
|
||||
fn default() -> Self {
|
||||
Self::new(UNIFIED_EXEC_OUTPUT_MAX_BYTES)
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadTailBuffer {
|
||||
/// Create a new buffer that retains at most `max_bytes` of output.
|
||||
///
|
||||
/// The retained output is split across a prefix ("head") and suffix ("tail")
|
||||
/// budget, dropping bytes from the middle once the limit is exceeded.
|
||||
pub(crate) fn new(max_bytes: usize) -> Self {
|
||||
let head_budget = max_bytes / 2;
|
||||
let tail_budget = max_bytes.saturating_sub(head_budget);
|
||||
Self {
|
||||
max_bytes,
|
||||
head_budget,
|
||||
tail_budget,
|
||||
head: Vec::new(),
|
||||
tail: VecDeque::new(),
|
||||
omitted_bytes: 0,
|
||||
}
|
||||
}
|
||||
impl<const MAX_BYTES: usize> HeadTailBuffer<MAX_BYTES> {
|
||||
const HEAD_BUDGET: usize = MAX_BYTES / 2;
|
||||
const TAIL_BUDGET: usize = MAX_BYTES.saturating_sub(Self::HEAD_BUDGET);
|
||||
|
||||
// Used for tests.
|
||||
#[allow(dead_code)]
|
||||
@@ -69,13 +46,13 @@ impl HeadTailBuffer {
|
||||
if chunk.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.max_bytes == 0 {
|
||||
if MAX_BYTES == 0 {
|
||||
self.omitted_bytes = self.omitted_bytes.saturating_add(chunk.len());
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill the head budget first, then keep a capped tail.
|
||||
let remaining_head = self.head_budget.saturating_sub(self.head.len());
|
||||
let remaining_head = Self::HEAD_BUDGET.saturating_sub(self.head.len());
|
||||
let head_len = remaining_head.min(chunk.len());
|
||||
if head_len > 0 {
|
||||
self.head.extend_from_slice(&chunk[..head_len]);
|
||||
@@ -120,17 +97,14 @@ impl HeadTailBuffer {
|
||||
/// contents while preserving its configured capacity.
|
||||
pub(crate) fn drain(&mut self) -> Self {
|
||||
Self {
|
||||
max_bytes: self.max_bytes,
|
||||
head_budget: self.head_budget,
|
||||
tail_budget: self.tail_budget,
|
||||
head: std::mem::take(&mut self.head),
|
||||
tail: std::mem::take(&mut self.tail),
|
||||
omitted_bytes: std::mem::take(&mut self.omitted_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append retained output from another buffer and preserve any omissions it
|
||||
/// already recorded.
|
||||
/// Append a later buffer with the same budget. This preserves the summary
|
||||
/// of the original concatenated output, including its omission count.
|
||||
pub(crate) fn push_buffer(&mut self, mut buffer: Self) {
|
||||
self.push_chunk(std::mem::take(&mut buffer.head));
|
||||
self.push_chunk(buffer.tail.drain(..).collect());
|
||||
@@ -141,15 +115,15 @@ impl HeadTailBuffer {
|
||||
if chunk.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.tail_budget == 0 {
|
||||
if Self::TAIL_BUDGET == 0 {
|
||||
self.omitted_bytes = self.omitted_bytes.saturating_add(chunk.len());
|
||||
return;
|
||||
}
|
||||
|
||||
if chunk.len() >= self.tail_budget {
|
||||
if chunk.len() >= Self::TAIL_BUDGET {
|
||||
// This single chunk is larger than the whole tail budget. Keep only the last
|
||||
// tail_budget bytes and drop everything else.
|
||||
let start = chunk.len().saturating_sub(self.tail_budget);
|
||||
let start = chunk.len().saturating_sub(Self::TAIL_BUDGET);
|
||||
let kept = &chunk[start..];
|
||||
let dropped = chunk.len().saturating_sub(kept.len());
|
||||
self.omitted_bytes = self
|
||||
@@ -166,7 +140,7 @@ impl HeadTailBuffer {
|
||||
}
|
||||
|
||||
fn trim_tail_to_budget(&mut self) {
|
||||
let excess = self.tail.len().saturating_sub(self.tail_budget);
|
||||
let excess = self.tail.len().saturating_sub(Self::TAIL_BUDGET);
|
||||
if excess > 0 {
|
||||
drop(self.tail.drain(..excess));
|
||||
self.omitted_bytes = self.omitted_bytes.saturating_add(excess);
|
||||
|
||||
@@ -4,7 +4,7 @@ use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn keeps_prefix_and_suffix_when_over_budget() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut buf = HeadTailBuffer::<10>::default();
|
||||
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
assert_eq!(buf.omitted_bytes(), 0);
|
||||
@@ -24,7 +24,7 @@ fn keeps_prefix_and_suffix_when_over_budget() {
|
||||
|
||||
#[test]
|
||||
fn max_bytes_zero_drops_everything() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 0);
|
||||
let mut buf = HeadTailBuffer::<0>::default();
|
||||
buf.push_chunk(b"abc".to_vec());
|
||||
|
||||
assert_eq!(buf.retained_bytes(), 0);
|
||||
@@ -34,7 +34,7 @@ fn max_bytes_zero_drops_everything() {
|
||||
|
||||
#[test]
|
||||
fn head_budget_zero_keeps_only_last_byte_in_tail() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 1);
|
||||
let mut buf = HeadTailBuffer::<1>::default();
|
||||
buf.push_chunk(b"abc".to_vec());
|
||||
|
||||
assert_eq!(buf.retained_bytes(), 1);
|
||||
@@ -44,12 +44,12 @@ fn head_budget_zero_keeps_only_last_byte_in_tail() {
|
||||
|
||||
#[test]
|
||||
fn draining_resets_state_and_push_buffer_preserves_omissions() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut buf = HeadTailBuffer::<10>::default();
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
buf.push_chunk(b"ab".to_vec());
|
||||
|
||||
let drained = buf.drain();
|
||||
let mut collected = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut collected = HeadTailBuffer::<10>::default();
|
||||
collected.push_buffer(drained);
|
||||
|
||||
assert_eq!(buf.retained_bytes(), 0);
|
||||
@@ -62,7 +62,7 @@ fn draining_resets_state_and_push_buffer_preserves_omissions() {
|
||||
|
||||
#[test]
|
||||
fn chunk_larger_than_tail_budget_keeps_only_tail_end() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut buf = HeadTailBuffer::<10>::default();
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
|
||||
// Tail budget is 5 bytes. This chunk should replace the tail and keep only its last 5 bytes.
|
||||
@@ -76,7 +76,7 @@ fn chunk_larger_than_tail_budget_keeps_only_tail_end() {
|
||||
|
||||
#[test]
|
||||
fn fills_head_then_tail_across_multiple_chunks() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut buf = HeadTailBuffer::<10>::default();
|
||||
|
||||
// Fill the 5-byte head budget across multiple chunks.
|
||||
buf.push_chunk(b"01".to_vec());
|
||||
@@ -97,7 +97,7 @@ fn fills_head_then_tail_across_multiple_chunks() {
|
||||
|
||||
#[test]
|
||||
fn empty_and_tiny_chunks_have_bounded_metadata() {
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
let mut buf = HeadTailBuffer::<10>::default();
|
||||
|
||||
for byte in b"0123456789ab" {
|
||||
buf.push_chunk(Vec::new());
|
||||
|
||||
@@ -324,7 +324,7 @@ async fn write_stdin(
|
||||
|
||||
#[test]
|
||||
fn push_chunk_preserves_prefix_and_suffix() {
|
||||
let mut buffer = HeadTailBuffer::default();
|
||||
let mut buffer: HeadTailBuffer = Default::default();
|
||||
buffer.push_chunk(vec![b'a'; UNIFIED_EXEC_OUTPUT_MAX_BYTES]);
|
||||
buffer.push_chunk(vec![b'b']);
|
||||
buffer.push_chunk(vec![b'c']);
|
||||
@@ -341,7 +341,7 @@ fn push_chunk_preserves_prefix_and_suffix() {
|
||||
|
||||
#[test]
|
||||
fn head_tail_buffer_default_preserves_prefix_and_suffix() {
|
||||
let mut buffer = HeadTailBuffer::default();
|
||||
let mut buffer: HeadTailBuffer = Default::default();
|
||||
buffer.push_chunk(vec![b'a'; UNIFIED_EXEC_OUTPUT_MAX_BYTES]);
|
||||
buffer.push_chunk(b"bc".to_vec());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user