charmbracelet / charmbracelet/x
vt: NewEmulator allocates a fixed 4 MiB parser buffer per emulator with no way to size it
- 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
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