terminal/src/host/PtySignalInputThread.cpp
Michael Niksa 56c35945b9
Rework locking and eventing during startup and shutdown to alleviate some VT issues (#2525)
Adjusts the startup and shutdown behavior of most threads in the console host to alleviate race conditions that are either exacerbated or introduced by the VT PTY threads.
2019-09-20 15:43:11 -07:00

184 lines
6.2 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "PtySignalInputThread.hpp"
#include "output.h"
#include "handle.h"
#include "..\interactivity\inc\ServiceLocator.hpp"
#include "..\terminal\adapter\DispatchCommon.hpp"
#define PTY_SIGNAL_RESIZE_WINDOW 8u
struct PTY_SIGNAL_RESIZE
{
unsigned short sx;
unsigned short sy;
};
using namespace Microsoft::Console;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::VirtualTerminal;
// Constructor Description:
// - Creates the PTY Signal Input Thread.
// Arguments:
// - hPipe - a handle to the file representing the read end of the VT pipe.
// - shutdownEvent - An event that will be signaled when the entire console is going down
// We can use this to know when to cancel what we're doing and cleanup.
PtySignalInputThread::PtySignalInputThread(wil::unique_hfile hPipe,
wil::shared_event shutdownEvent) :
_hFile{ std::move(hPipe) },
_shutdownEvent{ shutdownEvent },
_hThread{},
_pConApi{ std::make_unique<ConhostInternalGetSet>(ServiceLocator::LocateGlobals().getConsoleInformation()) },
_dwThreadId{ 0 },
_consoleConnected{ false }
{
THROW_HR_IF(E_HANDLE, !_shutdownEvent);
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
THROW_IF_NULL_ALLOC(_pConApi.get());
}
PtySignalInputThread::~PtySignalInputThread()
{
// Manually terminate our thread during unittesting. Otherwise, the test
// will finish, but TAEF will not manually kill the test.
#ifdef UNIT_TESTING
TerminateThread(_hThread.get(), 0);
#endif
}
// Function Description:
// - Static function used for initializing an instance's ThreadProc.
// Arguments:
// - lpParameter - A pointer to the PtySignalInputTHread instance that should be called.
// Return Value:
// - The return value of the underlying instance's _InputThread
DWORD WINAPI PtySignalInputThread::StaticThreadProc(_In_ LPVOID lpParameter)
{
PtySignalInputThread* const pInstance = reinterpret_cast<PtySignalInputThread*>(lpParameter);
return pInstance->_InputThread();
}
// Method Description:
// - Tell us that there's a client attached to the console, so we can actually
// do something with the messages we recieve now. Before this is set, there
// is no guarantee that a client has attached, so most parts of the console
// (in and screen buffers) haven't yet been initialized.
// Arguments:
// - <none>
// Return Value:
// - <none>
void PtySignalInputThread::ConnectConsole() noexcept
{
_consoleConnected = true;
}
// Method Description:
// - The ThreadProc for the PTY Signal Input Thread.
// Return Value:
// - S_OK if the thread runs to completion.
// - Otherwise it may cause an application termination another route and never return.
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread()
{
auto onExitTriggerShutdown = wil::scope_exit([&] {
_shutdownEvent.SetEvent();
});
while (true)
{
unsigned short signalId;
RETURN_IF_FAILED(_GetData(&signalId, sizeof(signalId)));
switch (signalId)
{
case PTY_SIGNAL_RESIZE_WINDOW:
{
PTY_SIGNAL_RESIZE resizeMsg = { 0 };
RETURN_IF_FAILED(_GetData(&resizeMsg, sizeof(resizeMsg)));
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash the new size in the launchArgs.
// We'll later use the value in launchArgs to set up the console buffer
if (!_consoleConnected)
{
short sColumns = 0;
short sRows = 0;
if (SUCCEEDED(UShortToShort(resizeMsg.sx, &sColumns)) &&
SUCCEEDED(UShortToShort(resizeMsg.sy, &sRows)) &&
(sColumns > 0 && sRows > 0))
{
ServiceLocator::LocateGlobals().launchArgs.SetExpectedSize({ sColumns, sRows });
}
break;
}
else
{
if (DispatchCommon::s_ResizeWindow(*_pConApi, resizeMsg.sx, resizeMsg.sy))
{
DispatchCommon::s_SuppressResizeRepaint(*_pConApi);
}
}
break;
}
default:
{
RETURN_HR(E_UNEXPECTED);
}
}
}
return S_OK;
}
// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.
// Arguments:
// - pBuffer - Buffer to fill with data.
// - cbBuffer - Count of bytes in the given buffer.
// Return Value:
// - True if data was retrieved successfully. False otherwise.
[[nodiscard]] HRESULT PtySignalInputThread::_GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer,
const DWORD cbBuffer)
{
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.
RETURN_IF_WIN32_BOOL_FALSE(ReadFile(_hFile.get(), pBuffer, cbBuffer, &dwRead, nullptr));
RETURN_HR_IF(E_UNEXPECTED, dwRead != cbBuffer);
return S_OK;
}
// Method Description:
// - Starts the PTY Signal input thread.
[[nodiscard]] HRESULT PtySignalInputThread::Start() noexcept
{
RETURN_LAST_ERROR_IF(!_hFile);
HANDLE hThread = nullptr;
// 0 is the right value, https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503
DWORD dwThreadId = 0;
hThread = CreateThread(nullptr,
0,
PtySignalInputThread::StaticThreadProc,
this,
0,
&dwThreadId);
RETURN_LAST_ERROR_IF_NULL(hThread);
_hThread.reset(hThread);
_dwThreadId = dwThreadId;
return S_OK;
}