charmbracelet / charmbracelet/x

vt: NewEmulator allocates a fixed 4 MiB parser buffer per emulator with no way to size it

Open
#973 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
314
Forks
94
Avg merge
3d 22h
Merged PRs (30d)
2

Description

## Summary

`vt.NewEmulator` allocates a fixed 4 MiB parser data buffer per emulator (`vt/emulator.go`: `t.parser.SetDataSize(1024 * 1024 * 4)`), and the `Emulator` exposes no way to change it. An application that holds many emulators pays 4.5 MiB each before a byte of output arrives.

## Measurement

`runtime.HeapAlloc` around N empty `NewEmulator(80, 24)` with `SetScrollbackSize(5000)`, at `x/vt v0.0.0-20260901172002-a5dee49b2863` and `x/ansi v0.11.8`:

| Emulators | Heap | Per emulator |
|---|---|---|
| 1 | 4 MiB | 4.50 MiB |
| 10 | 44 MiB | 4.50 MiB |
| 100 | 449 MiB | 4.50 MiB |

The scrollback buffer is a small part of that; the parser data buffer is the bulk.

## Context

We build a terminal multiplexer for coding agents where every session gets its own `vt.Emulator`; a fleet of a hundred sessions is a real workload. The 4 MiB buffer exists for large OSC payloads, which is the right thing to support, but most emulators never see one.

## Suggestion

Two small changes, either would help, both together would be ideal:

1. `x/vt`: let the caller set the parser data size. An option on the emulator (`SetParserDataSize(n)`) or a constructor that takes it, keeping 4 MiB as the default so nothing changes for existing users.
2. `x/ansi`: `Parser.SetDataSize(0)` already switches to a grow-on-demand buffer (`p.data = append(p.data, b)`), but that mode has no ceiling, so a hostile stream can grow without bound. A `SetDataLimit(n)` (or a second argument) that caps growth and drops the payload past it would make the dynamic mode safe to use by default: emulators would start near zero and grow only for the payloads they actually receive.

Happy to open a PR for either if the shape is acceptable.

Contributor guide

Open the contributing guide

Research direction

Start in vt/emulator.go at the NewEmulator call to parser.SetDataSize, then inspect x/ansi's Parser.SetDataSize behavior. Decide on the supported sizing API and ensure callers can avoid the fixed allocation while preserving the 4 MiB default; if dynamic growth is used, its limit should prevent unbounded payload growth.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
cli
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.