regression post 1.21: TUI app cursor position blocked until a key is hit - was working fine before #23163

Closed
opened 2026-01-31 08:34:09 +00:00 by claunia · 15 comments
Owner

Originally created by @ldemailly on GitHub (Apr 15, 2025).

Originally assigned to: @lhecker on GitHub.

Windows Terminal version

1.22+

Windows build number

10.0.26100.3775

Other Software

golang 1.23.8
fortio.org/terminal/fps 0.29.2

Steps to reproduce

install golang

go run fortio.org/terminal/fps@v0.29.2 -truecolor
Image

Expected Behavior

Used to work fine as in run continuously

Actual Behavior

Now runs in stop motion where you need to hit a key for next frame(s) to show up

Originally created by @ldemailly on GitHub (Apr 15, 2025). Originally assigned to: @lhecker on GitHub. ### Windows Terminal version 1.22+ ### Windows build number 10.0.26100.3775 ### Other Software golang 1.23.8 fortio.org/terminal/fps 0.29.2 ### Steps to reproduce install golang ``` go run fortio.org/terminal/fps@v0.29.2 -truecolor ``` <img width="787" alt="Image" src="https://github.com/user-attachments/assets/b354530b-761e-4c1a-8ee2-153bc8ace945" /> ### Expected Behavior Used to work fine as in run continuously ### Actual Behavior Now runs in stop motion where you need to hit a key for next frame(s) to show up
Author
Owner

@ldemailly commented on GitHub (Apr 15, 2025):

I am the author of fps so happy to explain how it works and/or make changes to it for it to become more "windows friendly" - for instance batching didn't work in 1.21 terminal (yet the fps was high/working) and dynamic resize doesn't work unlike in macos/linux

@ldemailly commented on GitHub (Apr 15, 2025): I am the author of `fps` so happy to explain how it works and/or make changes to it for it to become more "windows friendly" - for instance batching didn't work in 1.21 terminal (yet the fps was high/working) and dynamic resize doesn't work unlike in macos/linux
Author
Owner

@DHowett commented on GitHub (Apr 15, 2025):

Interesting... I wonder if it has the same kinetics as #18225. Does it suddenly start working after, say, 65 seconds?

Also, thanks for the report! 1.22 is the first release which uses the new "passthrough" console hosting subsystem (which employs either direct translation (for windows console APIs) or direct passthrough (for already-VT-encoded text), rather than a screen-based readout), so I am not terribly surprised that it's uncovered something.

@DHowett commented on GitHub (Apr 15, 2025): _Interesting_... I wonder if it has the same kinetics as #18225. Does it suddenly start working after, say, 65 seconds? Also, thanks for the report! 1.22 is the first release which uses the new "passthrough" console hosting subsystem (which employs either direct translation (for windows console APIs) or direct passthrough (for already-VT-encoded text), rather than a screen-based readout), so I am not terribly surprised that it's uncovered something.
Author
Owner

@ldemailly commented on GitHub (Apr 15, 2025):

Does it suddenly start working after, say, 65 seconds?

Nah it's just stuck indefinitely - it's possibly a bug in the way I read the input, in 1.21 stdin is non blocking while now it seems blocking (maybe I need to make it non blocking ?)

This is is with

go run fortio.org/terminal/brick@latest

It only moves if I hit a key

Image
@ldemailly commented on GitHub (Apr 15, 2025): > Does it suddenly start working after, say, 65 seconds? Nah it's just stuck indefinitely - it's possibly a bug in the way I read the input, in 1.21 stdin is non blocking while now it seems blocking (maybe I need to make it non blocking ?) This is is with ``` go run fortio.org/terminal/brick@latest ``` It only moves if I hit a key <img width="783" alt="Image" src="https://github.com/user-attachments/assets/4e1d87d2-f99d-47bd-b20e-d6e70e7c646d" />
Author
Owner

@lhecker commented on GitHub (Apr 15, 2025):

This could either be an issue with how we signal input availability in conhost/ConPTY, or I made a mistake with the overlapped IO signaling.

If you could shrink the repro to a small Go snippet that only reads from stdin (i.e. as little stdout as possible), I'd greatly appreciate it. This is because it would make debug tracing a lot less noisy.

@lhecker commented on GitHub (Apr 15, 2025): This could either be an issue with how we signal input availability in conhost/ConPTY, or I made a mistake with the overlapped IO signaling. If you could shrink the repro to a small Go snippet that only reads from stdin (i.e. as little stdout as possible), I'd greatly appreciate it. This is because it would make debug tracing a lot less noisy.
Author
Owner

@ldemailly commented on GitHub (Apr 15, 2025):

Done :)

go run github.com/ldemailly/go-scratch/windows_terminal_issue@v0.2.0

https://github.com/ldemailly/go-scratch/blob/main/windows_terminal_issue/issue18800.go

// https://github.com/microsoft/terminal/issues/18800

package main

import (
	"fmt"
	"os"
	"strings"

	"golang.org/x/term"
)

func RawPrintln(stuff ...any) {
	fmt.Print(stuff...)
	fmt.Print("\r\n")
}

func main() {
	fd := int(os.Stdin.Fd())
	oldState, err := term.MakeRaw(fd)
	if err != nil {
		fmt.Println("Error setting terminal to raw mode:", err)
		return
	}
	defer func() {
		err = term.Restore(fd, oldState)
		fmt.Println("Terminal restored to original , err", err)
	}()
	RawPrintln("Terminal in raw mode - 'q' to exit")
	buf := make([]byte, 16) // fits ansi arrow escape, unicode, etc
	iter := 1
	requestCursorPos := []byte("\033[6n")
	expected := len(requestCursorPos)
	for {
		nw, err := os.Stdout.Write([]byte(requestCursorPos))
		if err != nil {
			RawPrintln("Error writing to terminal:", err)
			return
		}
		if nw != expected {
			RawPrintln("Short write", nw, "vs", expected)
		}
		n, err := os.Stdin.Read(buf)
		if err != nil {
			RawPrintln("Error reading from terminal:", err)
			return
		}
		bufStr := string(buf[:n])
		// might fail with some ansi echo having a q in them,
		// but this is just a quick repro/test.
		if strings.ContainsRune(bufStr, 'q') {
			RawPrintln("\r\nExiting...")
			break
		}
		fmt.Printf("\r[%05d] Read %d bytes: %q      ", iter, n, bufStr)
		iter++
	}
}

The issue is requesting cursor position stops working after a bit on 1.22+ and then resumes working for a little bit when hitting a key

edit: typo corrected and a few fixes, also catch (unlikely/that's not the issue) short writes

@ldemailly commented on GitHub (Apr 15, 2025): Done :) ``` go run github.com/ldemailly/go-scratch/windows_terminal_issue@v0.2.0 ``` https://github.com/ldemailly/go-scratch/blob/main/windows_terminal_issue/issue18800.go ```go // https://github.com/microsoft/terminal/issues/18800 package main import ( "fmt" "os" "strings" "golang.org/x/term" ) func RawPrintln(stuff ...any) { fmt.Print(stuff...) fmt.Print("\r\n") } func main() { fd := int(os.Stdin.Fd()) oldState, err := term.MakeRaw(fd) if err != nil { fmt.Println("Error setting terminal to raw mode:", err) return } defer func() { err = term.Restore(fd, oldState) fmt.Println("Terminal restored to original , err", err) }() RawPrintln("Terminal in raw mode - 'q' to exit") buf := make([]byte, 16) // fits ansi arrow escape, unicode, etc iter := 1 requestCursorPos := []byte("\033[6n") expected := len(requestCursorPos) for { nw, err := os.Stdout.Write([]byte(requestCursorPos)) if err != nil { RawPrintln("Error writing to terminal:", err) return } if nw != expected { RawPrintln("Short write", nw, "vs", expected) } n, err := os.Stdin.Read(buf) if err != nil { RawPrintln("Error reading from terminal:", err) return } bufStr := string(buf[:n]) // might fail with some ansi echo having a q in them, // but this is just a quick repro/test. if strings.ContainsRune(bufStr, 'q') { RawPrintln("\r\nExiting...") break } fmt.Printf("\r[%05d] Read %d bytes: %q ", iter, n, bufStr) iter++ } } ``` The issue is requesting cursor position stops working after a bit on 1.22+ and then resumes working for a little bit when hitting a key edit: typo corrected and a few fixes, also catch (unlikely/that's not the issue) short writes
Author
Owner

@ldemailly commented on GitHub (Apr 16, 2025):

Btw if we change for { to for iter := range 100_000 this can be used as a regression tests, it passes in 1.21 (the program does ends) and on 1.22+ it hangs after about a few hundred iterations on my machine (a wide range actually got 11-719 in only 4 runs maybe some timer based issue)

@ldemailly commented on GitHub (Apr 16, 2025): Btw if we change `for {` to `for iter := range 100_000` this can be used as a regression tests, it passes in 1.21 (the program does ends) and on 1.22+ it hangs after about a few hundred iterations on my machine (a wide range actually got 11-719 in only 4 runs maybe some timer based issue)
Author
Owner

@ldemailly commented on GitHub (Apr 16, 2025):

I made that change - v0.3.0 has an optional -n for number of iterations, defaults to 100k, takes a few seconds to complete ok with terminal 1.21, hangs with latest after a random number of iterations between 0 and some thousands

go run github.com/ldemailly/go-scratch/windows_terminal_issue@v0.3.0

(b3e694d8d0/windows_terminal_issue/issue18800.go)

1.21.250204001 and earlier:

Image

vs 1.22,1.23 pre:

Image
@ldemailly commented on GitHub (Apr 16, 2025): I made that change - v0.3.0 has an optional -n for number of iterations, defaults to 100k, takes a few seconds to complete ok with terminal 1.21, hangs with latest after a random number of iterations between 0 and some thousands ``` go run github.com/ldemailly/go-scratch/windows_terminal_issue@v0.3.0 ``` (https://github.com/ldemailly/go-scratch/blob/b3e694d8d0433886cb8467c358f2b75fc5cfa310/windows_terminal_issue/issue18800.go) 1.21.250204001 and earlier: <img width="596" alt="Image" src="https://github.com/user-attachments/assets/875a7f4e-7144-4703-a45d-0765f99378ce" /> vs 1.22,1.23 pre: <img width="701" alt="Image" src="https://github.com/user-attachments/assets/10eb5ac5-2d3d-4ae1-8dca-fb77ce0e4ace" />
Author
Owner

@lhecker commented on GitHub (Apr 18, 2025):

Your repro finally gave me a chance to hunt down a hunch I've had for the longest time. It was relatively easy to fix, but the bad news is that the bug is extremely severe. It may be the cause for many if not all of the stdin issues we've had over the last decade. The good news is that it's fixed and I don't believe there's another major issue in that area.

@lhecker commented on GitHub (Apr 18, 2025): Your repro finally gave me a chance to hunt down a hunch I've had for the longest time. It was relatively easy to fix, but the bad news is that the bug is extremely severe. It may be the cause for many if not all of the stdin issues we've had over the last decade. The good news is that it's fixed and I don't believe there's another major issue in that area.
Author
Owner

@ldemailly commented on GitHub (Apr 18, 2025):

Great to hear! how come it only showed up in 1.22 ?

@ldemailly commented on GitHub (Apr 18, 2025): Great to hear! how come it only showed up in 1.22 ?
Author
Owner

@lhecker commented on GitHub (Apr 18, 2025):

It contains huge performance improvements for output processing (less so for input processing). I think what's happening is that you found a VT output (the DSC CPR) that now takes almost exactly as long to process as initiating the raw read, hence perfectly triggering the race condition now.

@lhecker commented on GitHub (Apr 18, 2025): It contains huge performance improvements for output processing (less so for input processing). I think what's happening is that you found a VT output (the DSC CPR) that now takes almost exactly as long to process as initiating the raw read, hence perfectly triggering the race condition now.
Author
Owner

@ldemailly commented on GitHub (Apr 18, 2025):

This is awesome, let me know where/when I can find a prerelease to try with the rest of my stuff (only need w11 64bits x86 if it matters) - Thanks! I'm very happy I was able to help.

@ldemailly commented on GitHub (Apr 18, 2025): This is awesome, let me know where/when I can find a prerelease to try with the rest of my stuff (only need w11 64bits x86 if it matters) - Thanks! I'm very happy I was able to help.
Author
Owner

@DHowett commented on GitHub (Apr 24, 2025):

This fix is out in Canary right now :)

@DHowett commented on GitHub (Apr 24, 2025): This fix is out in Canary right now :)
Author
Owner

@ldemailly commented on GitHub (May 18, 2025):

thanks again for fixing this. is there a tagged version that has the fix?

@ldemailly commented on GitHub (May 18, 2025): thanks again for fixing this. is there a tagged version that has the fix?
Author
Owner

@lhecker commented on GitHub (May 19, 2025):

There hasn't been a Stable or Preview release since that PR was merged. You could subscribe to release notifications on this project to get notified for that. 🙂

@lhecker commented on GitHub (May 19, 2025): There hasn't been a Stable or Preview release since that PR was merged. You could subscribe to release notifications on this project to get notified for that. 🙂
Author
Owner

@ldemailly commented on GitHub (May 19, 2025):

done, found the custom watch UI :)

@ldemailly commented on GitHub (May 19, 2025): done, found the custom watch UI :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#23163