Skip to content

LAB-1654: Cap vt parser data buffer - #16

Closed
cweill wants to merge 2 commits into
LAB-1803-trim-styled-scrollbackfrom
LAB-1654-parser-buffer
Closed

LAB-1654: Cap vt parser data buffer#16
cweill wants to merge 2 commits into
LAB-1803-trim-styled-scrollbackfrom
LAB-1654-parser-buffer

Conversation

@cweill

@cweill cweill commented May 14, 2026

Copy link
Copy Markdown

Motivation

vt.NewEmulator eagerly allocates a 4 MiB ANSI parser data buffer for every emulator. In amux this shows up as the largest fixed per-pane parser cost in heap profiles.

Summary

  • Add a regression test that checks the parser data buffer cap on new emulators and after an oversized OSC payload.
  • Reduce the vt emulator parser data buffer from 4 MiB to 128 KiB.
  • Keep parser ownership per emulator; no pooling or shared parser state is introduced.

Testing

  • go test -run TestNewEmulatorCapsParserDataBuffer -count=100
  • go test ./... -count=1

Review focus

Review the 128 KiB cap choice for OSC, DCS, SOS, PM, and APC payload retention. This PR changes only the parser data buffer size; it does not change terminal wire handling, output encoding, or parser ownership.

@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

Claude finished @cweill's task in 3m 3s —— View job


Review

  • Read changed files and parser implementation
  • Verify buffer cap enforcement logic
  • Evaluate test correctness
  • Assess the 128 KiB cap choice

Parser cap enforcement (ansi/parser.go)

The PutAction handler in performAction silently drops bytes once the buffer is full:

// parser.go:346-353
if p.dataLen < 0 {
    p.data = append(p.data, b)
} else {
    if p.dataLen < len(p.data) {
        p.data[p.dataLen] = b
        p.dataLen++
    }
}

In bounded mode (dataLen >= 0), p.dataLen tops out at len(p.data) = 128*1024. No corruption, no panic, no buffer growth. The cap is correctly enforced. ✓

emulator.go change

defaultParserDataSize is named well and the comment accurately describes the affected sequences (OSC, DCS, SOS, PM, APC). The call sequence NewParser() → SetDataSize(defaultParserDataSize) correctly overrides the parser's 64 KiB default. ✓

Note: ansi.NewParser() itself now defaults to 64 KiB (1024 * 64), so the emulator's explicit override to 128 KiB is intentional and twice the bare default.

Test review (vt/parser_buffer_test.go)

Assertion 1 — initial cap: Data() returns p.data[:p.dataLen] where p.dataLen = 0 on construction. cap(p.data[:0]) = cap(p.data) = 128*1024. Correct. ✓

Assertion 2cap after oversized write: DispatchAction (triggered by the BEL terminator) does not reset p.dataLen. After the write completes, p.dataLen = 128*1024 and cap(p.data[:128*1024]) = 128*1024. Verifies the buffer didn't grow past its initial allocation. ✓

Assertion 3len after oversized write: With p.dataLen = 128*1024 after dispatch, this checks 128*1024 > 128*1024 which is always false in the non-failing case. The check is still meaningful — it would fire if the parser grew the buffer (e.g. if someone inadvertently switched to unlimited mode). ✓

Minor: The test declares const wantParserDataSize = 128 * 1024 instead of referencing defaultParserDataSize (accessible since the test is in package vt). Using defaultParserDataSize directly would eliminate the duplication and make the test self-adjusting. Using a literal is also a valid regression pin. Not blocking.

Cap size: 128 KiB

Since you explicitly asked reviewers to evaluate this:

Fine for: window titles (OSC 0/1/2), hyperlinks (OSC 8), color queries (OSC 10/11), DECRQSS replies (DCS). All of these are typically ≪ 1 KiB.

Truncates: OSC 52 (clipboard read/write). A 100 KB clipboard payload encodes to ~136 KiB of base64, which exceeds the cap. The truncation is silent — the OSC 52 handler will receive a partial base64 string and the clipboard operation will fail without any error surfaced to the user. If amux forwards clipboard operations (e.g. via \x1b]52;c;<base64>\a), users pasting anything larger than ~96 KB of binary data will see silent failures.

Potentially truncates: Sixel graphics (DCS). A single sixel frame can easily exceed 128 KiB. If amux needs to render sixels through the emulator, they will be silently clipped.

If neither clipboard-over-terminal nor sixel rendering is in scope for amux today, 128 KiB is a reasonable cap. If they are in scope, consider 512 KiB or keep a separate per-feature limit.


LGTM

@greptile-apps

greptile-apps Bot commented May 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR reduces the per-emulator ANSI parser data buffer from 4 MiB to 128 KiB by introducing a named constant defaultParserDataSize, and adds a regression test that validates the cap on construction and after an oversized OSC payload.

  • vt/emulator.go: Replaces the inline 1024 * 1024 * 4 literal with defaultParserDataSize = 128 * 1024; no other parser ownership or wiring changes.
  • vt/parser_buffer_test.go: White-box test asserts the initial cap is 128 KiB and that writing a 256 KiB OSC title sequence does not cause the buffer to grow beyond 128 KiB; the fixed-size path in the parser never calls append, so the underlying array is never reallocated.

Confidence Score: 4/5

Safe to merge; the only behavioural change is the buffer ceiling, and the parser's fixed-size path never reallocates, so no heap surprise is possible.

The production change is a one-line constant substitution with no logic risk. The test correctly validates both the initial cap and the no-growth guarantee after an oversized payload, but it duplicates the magic number 128 * 1024 rather than referencing defaultParserDataSize, so a future change to the constant would leave the test silently asserting the old value.

vt/parser_buffer_test.go — the test constant should reference defaultParserDataSize instead of repeating the literal.

Important Files Changed

Filename Overview
vt/emulator.go Introduces defaultParserDataSize = 128 * 1024 and replaces the inline 4 MiB literal; correct and safe since the parser never reallocates in fixed-size mode.
vt/parser_buffer_test.go New white-box regression test for the 128 KiB cap; test logic is correct but duplicates the magic number rather than referencing the production constant.

Reviews (1): Last reviewed commit: "LAB-1654: Cap vt parser data buffer" | Re-trigger Greptile

Comment thread vt/parser_buffer_test.go
)

func TestNewEmulatorCapsParserDataBuffer(t *testing.T) {
const wantParserDataSize = 128 * 1024

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test constant duplicates production constant

The test declares its own wantParserDataSize = 128 * 1024 instead of referencing the unexported constant defaultParserDataSize defined in emulator.go. Because the test lives in package vt (not package vt_test), it has direct access to unexported identifiers. If defaultParserDataSize is ever updated, the test constant must be updated separately and in sync — a silent drift that this test is specifically designed to catch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant