-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wait for clients to exit on ConPTY shutdown #14282
Changes from 2 commits
8bccc33
6f177cf
a7db7a9
688a587
d693df4
f5f22ff
f24689f
7fb6ffa
bfd9004
0a1da70
fb14982
2644816
a0d0e68
4c2da9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,17 +96,29 @@ void PtySignalInputThread::CreatePseudoWindow() | |
// Return Value: | ||
// - S_OK if the thread runs to completion. | ||
// - Otherwise it may cause an application termination and never return. | ||
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread() | ||
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread() noexcept | ||
{ | ||
PtySignal signalId; | ||
while (_GetData(&signalId, sizeof(signalId))) | ||
const auto shutdown = wil::scope_exit([this]() { | ||
_Shutdown(); | ||
}); | ||
|
||
for (;;) | ||
{ | ||
PtySignal signalId; | ||
if (!_GetData(&signalId, sizeof(signalId))) | ||
{ | ||
return S_OK; | ||
} | ||
|
||
switch (signalId) | ||
{ | ||
case PtySignal::ShowHideWindow: | ||
{ | ||
ShowHideData msg = { 0 }; | ||
_GetData(&msg, sizeof(msg)); | ||
if (!_GetData(&msg, sizeof(msg))) | ||
{ | ||
return S_OK; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of these were missing. |
||
|
||
LockConsole(); | ||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); | ||
|
@@ -148,7 +160,10 @@ void PtySignalInputThread::CreatePseudoWindow() | |
case PtySignal::ResizeWindow: | ||
{ | ||
ResizeWindowData resizeMsg = { 0 }; | ||
_GetData(&resizeMsg, sizeof(resizeMsg)); | ||
if (!_GetData(&resizeMsg, sizeof(resizeMsg))) | ||
{ | ||
return S_OK; | ||
} | ||
|
||
LockConsole(); | ||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); | ||
|
@@ -171,7 +186,10 @@ void PtySignalInputThread::CreatePseudoWindow() | |
case PtySignal::SetParent: | ||
{ | ||
SetParentData reparentMessage = { 0 }; | ||
_GetData(&reparentMessage, sizeof(reparentMessage)); | ||
if (_GetData(&reparentMessage, sizeof(reparentMessage))) | ||
{ | ||
return S_OK; | ||
} | ||
|
||
LockConsole(); | ||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); | ||
|
@@ -191,12 +209,9 @@ void PtySignalInputThread::CreatePseudoWindow() | |
break; | ||
} | ||
default: | ||
{ | ||
THROW_HR(E_UNEXPECTED); | ||
} | ||
return S_OK; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't throw an exception here - no one is catching it and it would cross an ABI boundary otherwise. This is also why this function is now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could wrap the whole function in a Looks a bit ugly, but it's true, the |
||
} | ||
} | ||
return S_OK; | ||
} | ||
|
||
// Method Description: | ||
|
@@ -272,26 +287,21 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data) | |
bool PtySignalInputThread::_GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, | ||
const DWORD cbBuffer) | ||
{ | ||
if (!_hFile) | ||
{ | ||
return false; | ||
} | ||
|
||
DWORD dwRead = 0; | ||
// If we failed to read because the terminal broke our pipe (usually due | ||
// to dying itself), close gracefully with ERROR_BROKEN_PIPE. | ||
// Otherwise throw an exception. ERROR_BROKEN_PIPE is the only case that | ||
// we want to gracefully close in. | ||
if (FALSE == ReadFile(_hFile.get(), pBuffer, cbBuffer, &dwRead, nullptr)) | ||
{ | ||
auto lastError = GetLastError(); | ||
if (lastError == ERROR_BROKEN_PIPE) | ||
{ | ||
_Shutdown(); | ||
} | ||
else | ||
{ | ||
THROW_WIN32(lastError); | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
_hFile.reset(); | ||
return false; | ||
} | ||
else if (dwRead != cbBuffer) | ||
|
||
if (dwRead != cbBuffer) | ||
{ | ||
_Shutdown(); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that |
||
} | ||
|
||
return true; | ||
|
@@ -346,7 +356,4 @@ void PtySignalInputThread::_Shutdown() | |
// _before_ we RundownAndExit. | ||
LockConsole(); | ||
ProcessCtrlEvents(); | ||
|
||
// Make sure we terminate. | ||
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,7 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) | |
{ | ||
const auto pInstance = reinterpret_cast<VtInputThread*>(lpParameter); | ||
pInstance->_InputThread(); | ||
return S_OK; | ||
} | ||
|
||
// Method Description: | ||
|
@@ -110,10 +111,6 @@ void VtInputThread::DoReadInput(const bool throwOnFail) | |
DWORD dwRead = 0; | ||
auto fSuccess = !!ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); | ||
|
||
// If we failed to read because the terminal broke our pipe (usually due | ||
// to dying itself), close gracefully with ERROR_BROKEN_PIPE. | ||
// Otherwise throw an exception. ERROR_BROKEN_PIPE is the only case that | ||
// we want to gracefully close in. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is a copy/paste from |
||
if (!fSuccess) | ||
{ | ||
_exitRequested = true; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,10 @@ using namespace WEX::TestExecution; | |
|
||
class ConPtyTests | ||
{ | ||
TEST_CLASS(ConPtyTests); | ||
BEGIN_TEST_CLASS(ConPtyTests) | ||
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:15") // 15s timeout | ||
END_TEST_CLASS() | ||
|
||
const COORD defaultSize = { 80, 30 }; | ||
TEST_METHOD(CreateConPtyNoPipes); | ||
TEST_METHOD(CreateConPtyBadSize); | ||
|
@@ -218,63 +221,6 @@ void ConPtyTests::SurvivesOnBreakOutput() | |
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
} | ||
|
||
void ConPtyTests::DiesOnBreakBoth() | ||
{ | ||
PseudoConsole pty = { 0 }; | ||
wil::unique_handle outPipeOurSide; | ||
wil::unique_handle inPipeOurSide; | ||
wil::unique_handle outPipePseudoConsoleSide; | ||
wil::unique_handle inPipePseudoConsoleSide; | ||
SECURITY_ATTRIBUTES sa; | ||
sa.nLength = sizeof(sa); | ||
sa.bInheritHandle = TRUE; | ||
sa.lpSecurityDescriptor = nullptr; | ||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0)); | ||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0)); | ||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); | ||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); | ||
|
||
VERIFY_SUCCEEDED( | ||
_CreatePseudoConsole(defaultSize, | ||
inPipePseudoConsoleSide.get(), | ||
outPipePseudoConsoleSide.get(), | ||
0, | ||
&pty)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
}); | ||
|
||
DWORD dwExit; | ||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit)); | ||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
|
||
wil::unique_process_information piClient; | ||
std::wstring realCommand = L"cmd.exe"; | ||
VERIFY_SUCCEEDED(AttachPseudoConsole(&pty, realCommand, piClient.addressof())); | ||
|
||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit)); | ||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
|
||
// Close one of the pipes... | ||
VERIFY_IS_TRUE(CloseHandle(outPipeOurSide.get())); | ||
|
||
// ... Wait for a couple seconds, make sure the child is still alive. | ||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT); | ||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit)); | ||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
|
||
// Tricky - write some input to the pcon. We need to do this so conhost can | ||
// realize that the output pipe has broken. | ||
VERIFY_SUCCEEDED(WriteFile(inPipeOurSide.get(), L"a", sizeof(wchar_t), nullptr, nullptr)); | ||
|
||
// Close the other pipe, and make sure conhost dies | ||
VERIFY_IS_TRUE(CloseHandle(inPipeOurSide.get())); | ||
|
||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 10000), (DWORD)WAIT_OBJECT_0); | ||
VERIFY_IS_TRUE(GetExitCodeProcess(pty.hConPtyProcess, &dwExit)); | ||
VERIFY_ARE_NOT_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
} | ||
|
||
Comment on lines
-221
to
-277
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test doesn't work anymore because now conhost will keep running unless all clients have disconnected. But winconpty holds a console client handle inside |
||
void ConPtyTests::DiesOnClose() | ||
{ | ||
BEGIN_TEST_METHOD_PROPERTIES() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this in the end, so that the
_GetData
calls are all uniform and consistent. I just felt that this looks better...