terminal: Conhost hangs when a lot of content is output in a single write

Windows Terminal version

n/a

Windows build number

10.0.19041.1348

Other Software

No response

Steps to reproduce

  1. Open a cmd shell in conhost.
  2. Run the C++ example further down in the comments: link.

The Python example below does not reproduce the issue reliably on all systems.

1. Open a WSL shell in conhost. 2. Execute the Python script below.
import math
import os
import sys

size = os.popen('stty size', 'r').read().split()
h,w = tuple(int(n) for n in size)
mx = w//2
my = h//2 

def frame(i):
  s = '\033[H'
  for y in range(h):
    for x in range(w):
      dy,dx = y-my,x-mx
      a = math.atan2(dy*2,dx) + math.pi
      c = (int(a/math.pi*127)+i)%256
      s += '\033[38;2;%d;%d;%dm*' % (c,c,c)
  return s

sys.stdout.write('Generating content...\n')
s = '\033[?25l'
for i in range(512):
  s += frame(i)
s += '\033[?25h\033[m\033[H'

sys.stdout.write('Starting animation...\n')
sys.stdout.write(s)

This constructs a little VT animation which it outputs with a single write call.

Expected Behavior

You should see a rotating pattern, a bit like a radar scan. This is what it looks like in MinTTY (using wsltty):

https://user-images.githubusercontent.com/4181424/142741894-fb9637da-6046-4c7b-b4cb-6e0e2efc583f.mp4

You’ll see the same sort of thing in XTerm, VTE, Alacritty, Kitty, etc.

Actual Behavior

Conhost displays the first couple of lines of the first frame (if anything), then hangs for a couple of seconds, and eventually just shows the final frame.

Windows Terminal is a little better, in that it doesn’t hang, but you still don’t see the animation - just the final frame.

I believe the problem is that the buffer is locked for the entire time that the VT parser is processing a write operation, and the render thread can’t render anything when it can’t obtain the lock. If the write buffer is particularly large (as is the case here), then the renderer may be blocked for quite a long time.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 20 (6 by maintainers)

Commits related to this issue

Most upvoted comments

I don’t believe it’s all too difficult to make a mutex save for reentrancy. My approach is basically:

static thread_local ULONG recursionCount = 0;
static til::ticket_lock lock;

void LockConsole() {
    const auto rc = ++recursionCount;
    if (rc == 1) {
        lock.lock();
    }
}

void UnlockConsole()
{
    const auto rc = --recursionCount;
    if (rc == 0) {
        lock.unlock();
    }
}

It’s missing safety-checks for over- and underflow, but otherwise it works fine.

@j4james But at least one improvement will result out of this: As you can see in my video above, even 2kB chunks of data can cause the output to lag in conhost (and ConPTY) if an application is writing VT sufficiently fast. By switching to a fair mutex (til::ticket_lock) we can improve the user experience. I’m going to submit a PR for that soon. 🙂 The performance hit of that seems to be less than 1.5% in my benchmarks.

@j4james if the same rotate.exe is run from a WSL prompt the delay is clearly visible. Also, please notice how the last frame looks. There’s about 4-5 sec between Starting animation... and end of test:

https://user-images.githubusercontent.com/3933920/142979310-4bc162e6-6e3a-423b-9c53-0a82cc80f849.mp4