fix(input): pipe-based stdin cancellation for MSYS/mintty (v0.2.1)#12
Merged
Conversation
Problem: MockTerminal returns EOF immediately, causing inputReader goroutine to exit before the test can verify the running state. This created a race condition especially visible on Windows. Solution: Introduced blockingMockReader that blocks in Read() until Close() is called. This ensures the inputReader goroutine stays alive for the duration of the test, allowing proper verification of the idempotent restart behavior. The test now reliably passes on all platforms (Linux, macOS, Windows) without needing to skip it.
Replace direct stdin reading with os.Pipe relay to guarantee instant cancellation on all platforms including MSYS/mintty where UnblockStdinRead (WriteConsoleInputW) is a no-op. - Add relayLoop() copying stdin to pipe, readLoopPipe() reading from pipe - Cancel() closes pipe writer for immediate EOF on read end - SetReadDeadline + UnblockStdinRead as relay unblock fallbacks - WaitForShutdown() exposed through Reader for clean shutdown - stopInputReader() calls WaitForShutdown(), timeout 100ms to 200ms - Fallback to legacy direct-read if os.Pipe() fails
- Add sync.Once guard (closePipeWriter) preventing pipe fd double-close between Cancel() and relayLoop defer — eliminates fd reuse risk - Add 6 double-close protection tests: CancelThenRelayExit, RelayExitsBeforeCancel, ConcurrentCancelAndRelayExit (50 iterations), RapidCreateCancelCycles (100 iterations), CancelWithoutRead, MultipleWaitForShutdown - Update CHANGELOG.md, README.md, ROADMAP.md, TTY_CONTROL_GUIDE.md for v0.2.1 hotfix release
- golang.org/x/sys v0.37.0 -> v0.40.0 - golang.org/x/term v0.36.0 -> v0.39.0 - github.com/unilibs/uniwidth v0.1.0-beta -> v0.2.0 - github.com/spf13/cobra v1.8.0 -> v1.10.2 - github.com/spf13/pflag v1.0.5 -> v1.0.10 - Updated all 19 go.mod files across the monorepo
- style/examples/basic,complete: fix import path (style/api -> style) - drag-scroll,hover-highlight,wheel-scroll: add missing testing replace - cobra-cli: add replace directives for all Phoenix modules
- Remove GraphemeClusters() (internal-only, render uses uniseg directly) - Simplify StringWidth/ClusterWidth to delegate to uniwidth.StringWidth - Fix StringWidthWithConfig/ClusterWidthWithConfig: use grapheme-aware base width + per-rune EA Ambiguous delta (StringWidthWithOptions lacks grapheme awareness for emoji modifiers/ZWJ sequences) - Remove related tests and benchmarks for removed GraphemeClusters - core/go.mod now depends only on uniwidth v0.2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hotfix release v0.2.1 resolving critical stdin race condition on MSYS2/mintty (Git Bash on Windows).
os.Piperelay for instant cancellation on ALL platforms, including MSYS/mintty whereWriteConsoleInputWis a no-opsync.Onceguard on pipe writer prevents fd reuse bugs whenCancel()and relay defer raceInputReaderRestartIdempotenttestArchitecture
Cancellation is instant:
Cancel()closes pipe writer -> EOF on read end.SetReadDeadline+WriteConsoleInputWas relay unblock fallbacks.Files changed (12 files, +686 / -67)
Code (tea module):
cancelable_reader.go— Pipe relay +closePipeWriter()viasync.Oncecancelable_reader_test.go— 12 new tests (pipe relay + double-close)reader.go—WaitForShutdown()exposed through Readerprogram.go— STEP 4 (WaitForShutdown), timeout 200msstdin_unblock_other.go/stdin_unblock_windows.go— Updated docsgo.mod—x/sys,x/termpromoted to direct depsDocs:
CHANGELOG.md— v0.2.1 hotfix entryREADME.md— Status v0.2.1, "What's New" sectionROADMAP.md— Version, dates, key dates tableTTY_CONTROL_GUIDE.md— Version footerTest plan
go test ./tea/... -count=1)go vet ./tea/...cleango build ./...clean