Skip to content

Commit bcf2f0c

Browse files
committed
Disable adaptive read buffering for PTY processes
Large TUI redraws (Claude Code, pi on resize) can emit hundreds of KB in one write. Before Emacs 31, process-adaptive-read-buffering defaults to t, throttling the filter to ~40 KB/s for bursty processes — a 570 KB post-resize frame streams in over ~15 seconds, making ghostel feel like a slow cascade (issue #85). Let-bind process-adaptive-read-buffering to nil and raise read-process-output-max to at least 1 MB around make-process in both ghostel--spawn-pty and ghostel-compile--spawn. Both values are captured at make-process time so they must be bound there, not set afterward. vterm does the same for the same reason. On Emacs 31+ the first binding is a no-op (default already nil). The read-process-output-max bump (max of user value, 1 MB) applies everywhere and collapses a 570 KB frame from ~9 filter calls to 1.
1 parent aa4912d commit bcf2f0c

3 files changed

Lines changed: 56 additions & 0 deletions

File tree

ghostel-compile.el

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@ local machine happens to have)."
467467
;; Defeat pagers (git grep, etc.).
468468
"PAGER=")
469469
(copy-sequence process-environment)))
470+
;; See `ghostel--spawn-pty' for why these are set.
471+
(process-adaptive-read-buffering nil)
472+
(read-process-output-max (max read-process-output-max (* 1024 1024)))
470473
(proc (make-process
471474
:name "ghostel-compile"
472475
:buffer buffer

ghostel.el

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,6 +2433,16 @@ matches the PTY window size, and stores the process in
24332433
"COLORTERM=truecolor")
24342434
extra-env
24352435
process-environment))
2436+
;; Large TUI redraws (Claude Code, pi on resize) can emit
2437+
;; hundreds of KB in one write. Before Emacs 31,
2438+
;; `process-adaptive-read-buffering' defaults to t and
2439+
;; throttles the filter to ~40 KB/s for bursty processes,
2440+
;; making resize feel like a slow cascade.
2441+
;; Also raise the per-read cap so one filter call can
2442+
;; consume a full redraw frame. Both are captured at
2443+
;; `make-process' time, so they must be let-bound here.
2444+
(process-adaptive-read-buffering nil)
2445+
(read-process-output-max (max read-process-output-max (* 1024 1024)))
24362446
(proc (make-process
24372447
:name "ghostel"
24382448
:buffer (current-buffer)

test/ghostel-test.el

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5176,6 +5176,49 @@ integration script runs, so input echo must be enabled before exec."
51765176
(when (process-live-p proc)
51775177
(delete-process proc))))))))
51785178

5179+
(ert-deftest ghostel-test-spawn-pty-disables-adaptive-read-buffering ()
5180+
"`ghostel--spawn-pty' must disable adaptive read buffering and raise
5181+
`read-process-output-max'. Before Emacs 31 the former defaulted to t
5182+
and throttled bursty TUI redraws."
5183+
(let ((captured-adaptive 'unset)
5184+
(captured-max nil)
5185+
(orig-make-process (symbol-function #'make-process)))
5186+
(cl-letf (((symbol-function #'make-process)
5187+
(lambda (&rest plist)
5188+
(setq captured-adaptive process-adaptive-read-buffering
5189+
captured-max read-process-output-max)
5190+
(apply orig-make-process plist))))
5191+
(with-temp-buffer
5192+
(let ((proc (ghostel--spawn-pty "/bin/sh" nil 24 80
5193+
"-ixon" nil nil)))
5194+
(unwind-protect
5195+
(progn
5196+
(should (null captured-adaptive))
5197+
(should (>= captured-max (* 1024 1024))))
5198+
(when (process-live-p proc)
5199+
(delete-process proc))))))))
5200+
5201+
(ert-deftest ghostel-test-compile-spawn-disables-adaptive-read-buffering ()
5202+
"`ghostel-compile--spawn' must disable adaptive read buffering and
5203+
raise `read-process-output-max'. Same reason as
5204+
`ghostel--spawn-pty' (issue #85)."
5205+
(let ((captured-adaptive 'unset)
5206+
(captured-max nil)
5207+
(orig-make-process (symbol-function #'make-process)))
5208+
(cl-letf (((symbol-function #'make-process)
5209+
(lambda (&rest plist)
5210+
(setq captured-adaptive process-adaptive-read-buffering
5211+
captured-max read-process-output-max)
5212+
(apply orig-make-process plist))))
5213+
(with-temp-buffer
5214+
(let ((proc (ghostel-compile--spawn "true" (current-buffer) 24 80)))
5215+
(unwind-protect
5216+
(progn
5217+
(should (null captured-adaptive))
5218+
(should (>= captured-max (* 1024 1024))))
5219+
(when (process-live-p proc)
5220+
(delete-process proc))))))))
5221+
51795222
;; -----------------------------------------------------------------------
51805223
;; Tests: window resize
51815224
;; -----------------------------------------------------------------------

0 commit comments

Comments
 (0)