Skip to content

ConPTY hangs or enters infinite loop when writing a 2-column character to a 1-column wide terminal #19922

@gcrtnst

Description

@gcrtnst

Windows Terminal version

1.23.20211.0

Windows build number

10.0.19045.6937 and 10.0.26200.7840

Other Software

No response

Steps to reproduce

Input a 2-column wide character (e.g., "キ" / U+30AD) into a ConPTY instance configured with a screen width of 1 column.

Reproduction Code:

#include <windows.h>
#include <stdio.h>
#include <process.h>

#pragma comment(lib, "kernel32.lib")

// Thread function to drain ConPTY output
unsigned int __stdcall OutputThread(void* pParam) {
	HANDLE hRead = (HANDLE)pParam;
	char buffer[4096];
	DWORD bytesRead;

	while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {}
	return 0;
}

int main() {
	HRESULT hr = S_OK;
	HANDLE hPipeInRead = NULL, hPipeInWrite = NULL;
	HANDLE hPipeOutRead = NULL, hPipeOutWrite = NULL;

	CreatePipe(&hPipeInRead, &hPipeInWrite, NULL, 0);
	CreatePipe(&hPipeOutRead, &hPipeOutWrite, NULL, 0);
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, OutputThread, hPipeOutRead, 0, NULL);

	HPCON hPC = NULL;
	COORD consoleSize = { 1, 20 };
	hr = CreatePseudoConsole(consoleSize, hPipeInRead, hPipeOutWrite, 0, &hPC);
	if (FAILED(hr)) return (int)hr;

	CloseHandle(hPipeInRead);
	CloseHandle(hPipeOutWrite);

	STARTUPINFOEXW si = { 0 };
	si.StartupInfo.cb = sizeof(STARTUPINFOEXW);

	SIZE_T attrListSize = 0;
	InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
	si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrListSize);
	InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrListSize);
	UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof(HPCON), NULL, NULL);

	PROCESS_INFORMATION pi = { 0 };
	wchar_t cmdLine[] = L"cmd.exe";
	CreateProcessW(NULL, cmdLine, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);

	const char* inputChar = "\xe3\x82\xad";	// "キ" in UTF-8
	DWORD bytesWritten;
	WriteFile(hPipeInWrite, inputChar, (DWORD)strlen(inputChar), &bytesWritten, NULL);

	Sleep(500);	// Give cmd.exe a moment to process before closing
	ClosePseudoConsole(hPC);

	WaitForSingleObject(hThread, INFINITE);

	CloseHandle(hThread);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	CloseHandle(hPipeInWrite);
	CloseHandle(hPipeOutRead);
	HeapFree(GetProcessHeap(), 0, si.lpAttributeList);

	printf("Execution finished cleanly.\n");
	return 0;
}

This bug appears to be causing resource leaks in the Vim test suite.
vim/vim#19013

Expected Behavior

The process should close gracefully without hanging, even if the character cannot be displayed correctly in a 1-column space.

Actual Behavior

The behavior varies slightly depending on the OS version, but both result in a hang:

  • Windows 10 (10.0.19045.6937): ClosePseudoConsole() never returns. ConPTY continuously emits the string \r\n (CRLF + space), and EOF is never reached on the output pipe.

  • Windows 11 (10.0.26200.7840): ClosePseudoConsole() returns immediately (as it does not wait for the ConPTY process to terminate). However, the output pipe continues to receive the \r\n sequence indefinitely. Consequently, the output reading thread never terminates, causing the main process to hang at WaitForSingleObject(hThread, INFINITE).

The reason Vim experiences a conhost.exe leak (vim/vim#19013) instead of a hang is probably due to ClosePseudoConsole() returning immediately on Windows 11 24H2+, combined with Vim’s (unusual) logic of closing pipes prior to calling ClosePseudoConsole() when shutting down a terminal.

Metadata

Metadata

Assignees

Labels

Issue-BugIt either shouldn't be doing this or needs an investigation.Needs-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions