Skip to content

Fix flaky FastTerminalReentrancyTest by avoiding PTY creation - #12970

Merged
gnodet merged 2 commits into
masterfrom
quick-fix/flaky-fast-terminal-test
Aug 31, 2026
Merged

Fix flaky FastTerminalReentrancyTest by avoiding PTY creation#12970
gnodet merged 2 commits into
masterfrom
quick-fix/flaky-fast-terminal-test

Conversation

@gnodet

@gnodet gnodet commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Force providers("exec") in the test's TerminalBuilder configuration so JLine creates a lightweight ExternalTerminal instead of a PosixPtyTerminal via the FFM provider

Root cause

With system(false), TerminalBuilder.doBuild() enters the non-system path which ignores the dumb(true) flag and iterates FFM/JNI/exec providers. The FFM provider calls CLibrary.openpty(), creating a real PTY pair (/dev/pts/N) and a PosixPtyTerminal.

Then build() calls supportsGraphemeClusterMode()ensureModesProbed()probeModes()readTerminalResponse()readProbeChar(). This sends DECRQM/DA1 escape sequences to the PTY and reads responses via NonBlockingReader.read(timeout), which delegates to FileInputStream.read0() — a native blocking read on the PTY master fd.

Although VMIN=0, VTIME=0 terminal attributes are set before probing (which should make reads return immediately when no data is available), there is a race: on some iterations, the native read blocks indefinitely instead of respecting the timeout. Thread dump from a local reproduction:

fast-terminal-thread [RUNNABLE]
  at java.io.FileInputStream.read0(Native Method)       ← stuck in kernel read
  at java.io.FilterInputStream.read(FilterInputStream.java:71)
  at org.jline.terminal.impl.AbstractPty$1.read(AbstractPty.java:87)
  at ...NonBlockingInputStream.read(NonBlockingInputStream.java:195)
  at ...NonBlockingReader.read(NonBlockingReader.java:208)
  at ...AbstractTerminal.readProbeChar(AbstractTerminal.java:939)
  at ...AbstractTerminal.readTerminalResponse(AbstractTerminal.java:961)
  at ...AbstractTerminal.probeModes(AbstractTerminal.java:543)
  at ...AbstractTerminal.ensureModesProbed(AbstractTerminal.java:502)
  at ...AbstractTerminal.probeGraphemeClusterMode(AbstractTerminal.java:745)
  at ...AbstractTerminal.supportsGraphemeClusterMode(AbstractTerminal.java:686)
  at ...TerminalBuilder.build(TerminalBuilder.java:898)

The exec provider's ExternalTerminal avoids all of this: no PTY, no pump threads, and supportsGraphemeClusterMode() returns false without any I/O.

Failed builds: https://github.com/apache/maven/actions/runs/33349929091/job/99362003331

Test plan

  • mvn test -pl impl/maven-jline -Dtest=FastTerminalReentrancyTest passes (5/5 tests, ~0.4s)
  • 10 consecutive runs: 10/10 pass with the fix; 1/10 hangs without the fix
  • Full impl/maven-jline module tests pass
  • CI build passes

🤖 Generated with Claude Code

Force providers("exec") in the test's TerminalBuilder configuration so
that JLine creates a lightweight ExternalTerminal instead of a
PosixPtyTerminal via the FFM provider. With system(false), the FFM
provider calls CLibrary.openpty() which creates a real PTY pair; the
subsequent grapheme-cluster probe (ensureModesProbed) then blocks on the
idle PTY master for the full probe timeout, and the PTY's pump threads
can stall close() during teardown -- all of which makes the tests flaky
on slow CI machines. The exec provider's ExternalTerminal avoids all of
this: no PTY, no pump threads, and supportsGraphemeClusterMode() returns
false immediately.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The native FileInputStream.read0() on the PTY master fd blocks
indefinitely on some iterations despite VMIN=0/VTIME=0 attributes,
rather than merely being slow.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@gnodet

gnodet commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Upstream JLine bug filed: jline/jline3#2209

The root cause is a race condition in JLine's AbstractTerminal.readProbeChar() where FileInputStream.read0() on the PTY master fd blocks indefinitely despite VMIN=0/VTIME=0 attributes being set. This PR works around it by forcing the exec provider to avoid PTY creation entirely.

@gnodet
gnodet requested review from cstamas and slachiewicz August 31, 2026 08:36
@gnodet
gnodet marked this pull request as ready for review August 31, 2026 10:03

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well-documented fix for the flaky FastTerminalReentrancyTest. The Javadoc clearly explains the root cause (FFM provider's openpty() creates a real PTY, and the grapheme-cluster probe's native blocking read races with the timeout), and .providers("exec") is the right minimal fix to force ExternalTerminal creation.

Identical change already reviewed and approved on the 4.0.x cherry-pick (#12971). No issues found.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of Guillaume Nodet

@gnodet
gnodet merged commit b414d93 into master Aug 31, 2026
42 of 43 checks passed
@gnodet
gnodet deleted the quick-fix/flaky-fast-terminal-test branch August 31, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants