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.
This commit is contained in:
Michael Niksa 2019-09-20 15:43:11 -07:00 committed by GitHub
parent 9102c5d030
commit 56c35945b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 735 additions and 291 deletions

View file

@ -26,13 +26,18 @@ using namespace Microsoft::Console::VirtualTerminal;
// - Creates the PTY Signal Input Thread.
// Arguments:
// - hPipe - a handle to the file representing the read end of the VT pipe.
PtySignalInputThread::PtySignalInputThread(_In_ wil::unique_hfile hPipe) :
// - 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());
}
@ -79,15 +84,21 @@ void PtySignalInputThread::ConnectConsole() noexcept
// - Otherwise it may cause an application termination another route and never return.
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread()
{
unsigned short signalId;
while (_GetData(&signalId, sizeof(signalId)))
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 };
_GetData(&resizeMsg, sizeof(resizeMsg));
RETURN_IF_FAILED(_GetData(&resizeMsg, sizeof(resizeMsg)));
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
@ -117,7 +128,7 @@ void PtySignalInputThread::ConnectConsole() noexcept
}
default:
{
THROW_HR(E_UNEXPECTED);
RETURN_HR(E_UNEXPECTED);
}
}
}
@ -132,34 +143,19 @@ void PtySignalInputThread::ConnectConsole() noexcept
// - cbBuffer - Count of bytes in the given buffer.
// Return Value:
// - True if data was retrieved successfully. False otherwise.
bool PtySignalInputThread::_GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer,
const DWORD cbBuffer)
[[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.
if (FALSE == ReadFile(_hFile.get(), pBuffer, cbBuffer, &dwRead, nullptr))
{
DWORD lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE)
{
_Shutdown();
return false;
}
else
{
THROW_WIN32(lastError);
}
}
else if (dwRead != cbBuffer)
{
_Shutdown();
return false;
}
RETURN_IF_WIN32_BOOL_FALSE(ReadFile(_hFile.get(), pBuffer, cbBuffer, &dwRead, nullptr));
return true;
RETURN_HR_IF(E_UNEXPECTED, dwRead != cbBuffer);
return S_OK;
}
// Method Description:
@ -185,31 +181,3 @@ bool PtySignalInputThread::_GetData(_Out_writes_bytes_(cbBuffer) void* const pBu
return S_OK;
}
// Method Description:
// - Perform a shutdown of the console. This happens when the signal pipe is
// broken, which means either the parent terminal process has died, or they
// called ClosePseudoConsole.
// CloseConsoleProcessState is not enough by itself - it will disconnect
// clients as if the X was pressed, but then we need to actually still die,
// so then call RundownAndExit.
// Arguments:
// - <none>
// Return Value:
// - <none>
void PtySignalInputThread::_Shutdown()
{
// Trigger process shutdown.
CloseConsoleProcessState();
// If we haven't terminated by now, that's because there's a client that's still attached.
// Force the handling of the control events by the attached clients.
// As of MSFT:19419231, CloseConsoleProcessState will make sure this
// happens if this method is called outside of lock, but if we're
// currently locked, we want to make sure ctrl events are handled
// _before_ we RundownAndExit.
ProcessCtrlEvents();
// Make sure we terminate.
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE);
}

View file

@ -20,7 +20,8 @@ namespace Microsoft::Console
class PtySignalInputThread final
{
public:
PtySignalInputThread(_In_ wil::unique_hfile hPipe);
PtySignalInputThread(wil::unique_hfile hPipe,
wil::shared_event shutdownEvent);
~PtySignalInputThread();
[[nodiscard]] HRESULT Start() noexcept;
@ -34,8 +35,9 @@ namespace Microsoft::Console
private:
[[nodiscard]] HRESULT _InputThread();
bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
void _Shutdown();
[[nodiscard]] HRESULT _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
wil::shared_event _shutdownEvent;
wil::unique_hfile _hFile;
wil::unique_handle _hThread;

View file

@ -24,15 +24,16 @@ using namespace Microsoft::Console::VirtualTerminal;
// - hPipe - a handle to the file representing the read end of the VT pipe.
// - inheritCursor - a bool indicating if the state machine should expect a
// cursor positioning sequence. See MSFT:15681311.
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
VtInputThread::VtInputThread(wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const bool inheritCursor) :
_hFile{ std::move(hPipe) },
_shutdownEvent{ shutdownEvent },
_hThread{},
_utf8Parser{ CP_UTF8 },
_dwThreadId{ 0 },
_exitRequested{ false },
_exitResult{ S_OK }
_dwThreadId{ 0 }
{
THROW_HR_IF(E_HANDLE, !_shutdownEvent);
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
@ -45,6 +46,30 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
_pInputStateMachine = std::make_unique<StateMachine>(engine.release());
THROW_IF_NULL_ALLOC(_pInputStateMachine.get());
_shutdownWatchdog = std::async(std::launch::async, [&] {
_shutdownEvent.wait();
if (_dwThreadId != 0)
{
wil::unique_handle threadHandle(OpenThread(STANDARD_RIGHTS_ALL | THREAD_TERMINATE, FALSE, _dwThreadId));
LOG_LAST_ERROR_IF_NULL(threadHandle.get());
if (threadHandle)
{
LOG_IF_WIN32_BOOL_FALSE(CancelSynchronousIo(threadHandle.get()));
}
}
});
}
VtInputThread::~VtInputThread()
{
if (_shutdownEvent)
{
_shutdownEvent.SetEvent();
// Wait for watchdog future to get the memo or we might try to destroy it before it gets to work.
_shutdownWatchdog.wait();
}
}
// Method Description:
@ -96,44 +121,54 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
return pInstance->_InputThread();
}
// Method Description:
// - Do a single ReadFile from our pipe, and try and handle it. If handling
// failed, throw or log, depending on what the caller wants.
// Routine Description:
// - A public way of pumping a single input message through the VT input channel
// - Reading input can be a blocking operation. This function will capture
// the thread ID of whomever calls it so it can be unblocked on shutdown events
// by a watchdog thread.
// - This function cannot be called by two public methods simultaneously.
// If another is already waiting in a blocked read on the VT input thread,
// an invalid state error will be returned.
// - This function is only valid during startup. Once the real VtInputThread starts
// to process the input, it will fill the thread ID field permanently until shutdown.
// Arguments:
// - throwOnFail: If true, throw an exception if there was an error processing
// the input recieved. Otherwise, log the error.
// Return Value:
// - <none>
void VtInputThread::DoReadInput(const bool throwOnFail)
// Return Value:
// - S_OK, a ReadFile error, an error processing input, or an invalid state error if another thread is already waiting.
[[nodiscard]] HRESULT VtInputThread::DoReadInput()
{
// If there's already a thread pumping VT input, it's not valid to read this from the outside.
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), _dwThreadId != 0);
// Store which thread is attempting to read VT input. It may get blocked indefinitely and need
// to get unstuck by a shutdown event.
_dwThreadId = GetCurrentThreadId();
// Set it back to 0 on the way out.
auto restoreThreadId = wil::scope_exit([&] {
_dwThreadId = 0;
});
// Perform the blocking read operation.
return _ReadInput();
}
// Method Description:
// - Do a single ReadFile from our pipe, and try and handle it.
// Arguments:
// - <none>
// Return Value:
// - S_OK or relevant error
[[nodiscard]] HRESULT VtInputThread::_ReadInput()
{
byte buffer[256];
DWORD dwRead = 0;
bool 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.
if (!fSuccess)
{
_exitRequested = true;
_exitResult = HRESULT_FROM_WIN32(GetLastError());
return;
}
RETURN_IF_WIN32_BOOL_FALSE(ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr));
HRESULT hr = _HandleRunInput(buffer, dwRead);
if (FAILED(hr))
{
if (throwOnFail)
{
_exitResult = hr;
_exitRequested = true;
}
else
{
LOG_IF_FAILED(hr);
}
}
RETURN_IF_FAILED(_HandleRunInput(buffer, dwRead));
return S_OK;
}
// Method Description:
@ -145,13 +180,19 @@ void VtInputThread::DoReadInput(const bool throwOnFail)
// have caused us to exit.
DWORD VtInputThread::_InputThread()
{
while (!_exitRequested)
{
DoReadInput(true);
}
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput();
auto onExitTriggerShutdown = wil::scope_exit([&] {
_shutdownEvent.SetEvent();
});
return _exitResult;
while (true)
{
// NOTE: From inside the thread itself, we don't need to stash the thread handle each call
// because it was done permanently for us when the thread was created. No one else is allowed
// in through the public method while the actual VtInputThread is running. Only during startup.
RETURN_IF_FAILED(_ReadInput());
}
return S_OK;
}
// Method Description:
@ -173,6 +214,9 @@ DWORD VtInputThread::_InputThread()
RETURN_LAST_ERROR_IF_NULL(hThread);
_hThread.reset(hThread);
// This will permanently shut the door on the public read method until shutdown.
// Once the thread is servicing messages, we don't want any other threads getting in here.
_dwThreadId = dwThreadId;
return S_OK;

View file

@ -22,23 +22,29 @@ namespace Microsoft::Console
class VtInputThread
{
public:
VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor);
VtInputThread(wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const bool inheritCursor);
~VtInputThread();
[[nodiscard]] HRESULT Start();
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);
void DoReadInput(const bool throwOnFail);
[[nodiscard]] HRESULT DoReadInput();
private:
[[nodiscard]] HRESULT _HandleRunInput(_In_reads_(cch) const byte* const charBuffer, const int cch);
DWORD _InputThread();
[[nodiscard]] HRESULT _ReadInput();
wil::shared_event _shutdownEvent;
std::future<void> _shutdownWatchdog;
wil::unique_hfile _hFile;
wil::unique_handle _hThread;
DWORD _dwThreadId;
bool _exitRequested;
HRESULT _exitResult;
std::unique_ptr<Microsoft::Console::VirtualTerminal::StateMachine> _pInputStateMachine;
Utf8ToWideCharParser _utf8Parser;
};

View file

@ -11,7 +11,7 @@
#include "../renderer/base/renderer.hpp"
#include "../types/inc/utils.hpp"
#include "input.h" // ProcessCtrlEvents
#include "handle.h" // LockConsole and UnlockConsole
#include "output.h" // CloseConsoleProcessState
using namespace Microsoft::Console;
@ -25,10 +25,24 @@ VtIo::VtIo() :
_initialized(false),
_objectsCreated(false),
_lookingForCursorPosition(false),
_IoMode(VtIoMode::INVALID)
_IoMode(VtIoMode::INVALID),
#ifdef UNIT_TESTING
_doNotTerminate(false),
#endif
_shutdownEvent()
{
}
VtIo::~VtIo()
{
if (_shutdownEvent)
{
_shutdownEvent.SetEvent();
// Wait for watchdog future to get the memo or we might try to destroy it before it gets to work.
_shutdownWatchdog.wait();
}
}
// Routine Description:
// Tries to get the VtIoMode from the given string. If it's not one of the
// *_STRING constants in VtIoMode.hpp, then it returns E_INVALIDARG.
@ -117,9 +131,35 @@ VtIo::VtIo() :
_hOutput.reset(OutHandle);
_hSignal.reset(SignalHandle);
// Since we have a valid request for a PTY, set up the events and watchdog mechanisms
// required to tear everything apart correctly at the end of a PTY session.
_shutdownEvent.create(wil::EventOptions::ManualReset);
_shutdownWatchdog = std::async(std::launch::async, [=] {
_shutdownEvent.wait();
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
#ifdef UNIT_TESTING
// Don't close process state if we're unit testing and this has been set by a friend because
// it will trigger the rundown and exit TerminateProcess killing the test host!
if (_doNotTerminate)
{
return;
}
#endif
CloseConsoleProcessState();
});
// We also need to register to know when the last process is exiting.
ServiceLocator::LocateGlobals().getConsoleInformation().ProcessHandleList.RegisterForNotifyOnLastFree(std::bind(&VtIo::_OnLastProcessExit, this));
// The only way we're initialized is if the args said we're in conpty mode.
// If the args say so, then at least one of in, out, or signal was specified
_initialized = true;
return S_OK;
}
@ -146,7 +186,7 @@ VtIo::VtIo() :
{
if (IsValidHandle(_hInput.get()))
{
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _shutdownEvent, _lookingForCursorPosition);
}
if (IsValidHandle(_hOutput.get()))
@ -158,6 +198,7 @@ VtIo::VtIo() :
{
case VtIoMode::XTERM_256:
_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(_hOutput),
_shutdownEvent,
gci,
initialViewport,
gci.GetColorTable(),
@ -165,6 +206,7 @@ VtIo::VtIo() :
break;
case VtIoMode::XTERM:
_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(_hOutput),
_shutdownEvent,
gci,
initialViewport,
gci.GetColorTable(),
@ -173,6 +215,7 @@ VtIo::VtIo() :
break;
case VtIoMode::XTERM_ASCII:
_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(_hOutput),
_shutdownEvent,
gci,
initialViewport,
gci.GetColorTable(),
@ -181,6 +224,7 @@ VtIo::VtIo() :
break;
case VtIoMode::WIN_TELNET:
_pVtRenderEngine = std::make_unique<WinTelnetEngine>(std::move(_hOutput),
_shutdownEvent,
gci,
initialViewport,
gci.GetColorTable(),
@ -189,10 +233,6 @@ VtIo::VtIo() :
default:
return E_FAIL;
}
if (_pVtRenderEngine)
{
_pVtRenderEngine->SetTerminalOwner(this);
}
}
}
CATCH_RETURN();
@ -245,18 +285,29 @@ bool VtIo::IsUsingVt() const
// We need both handles for this initialization to work. If we don't have
// both, we'll skip it. They either aren't going to be reading output
// (so they can't get the DSR) or they can't write the response to us.
// GH Microsoft/Terminal#1810:
// - We can't just infinite loop and let them hang. It needs to recognize an error
// condition otherwise it can be getting notified on another thread of a proper shutdown
// and be unable to actually leave this loop and let go of the lock.
if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread)
{
LOG_IF_FAILED(_pVtRenderEngine->RequestCursor());
while (_lookingForCursorPosition)
RETURN_IF_FAILED(_pVtRenderEngine->RequestCursor());
// The consequences of the VT Input
// receiving its cursor position information cause additional entrances to the global
// lock that we're already holding on the IO thread as a part of initialization here.
// So we have to pump the read manually here until we get what we're looking for.
while (!_shutdownEvent.is_signaled() && _lookingForCursorPosition)
{
_pVtInputThread->DoReadInput(false);
RETURN_IF_FAILED(_pVtInputThread->DoReadInput());
}
}
// We can't start the VT input thread until after we've pumped it manually
// (if necessary) above for the cursor response.
if (_pVtInputThread)
{
LOG_IF_FAILED(_pVtInputThread->Start());
RETURN_IF_FAILED(_pVtInputThread->Start());
}
if (_pPtySignalInputThread)
@ -293,7 +344,7 @@ bool VtIo::IsUsingVt() const
{
try
{
_pPtySignalInputThread = std::make_unique<PtySignalInputThread>(std::move(_hSignal));
_pPtySignalInputThread = std::make_unique<PtySignalInputThread>(std::move(_hSignal), _shutdownEvent);
// Start it if it was successfully created.
RETURN_IF_FAILED(_pPtySignalInputThread->Start());
@ -345,59 +396,6 @@ bool VtIo::IsUsingVt() const
return hr;
}
void VtIo::CloseInput()
{
// This will release the lock when it goes out of scope
std::lock_guard<std::mutex> lk(_shutdownLock);
_pVtInputThread = nullptr;
_ShutdownIfNeeded();
}
void VtIo::CloseOutput()
{
// This will release the lock when it goes out of scope
std::lock_guard<std::mutex> lk(_shutdownLock);
Globals& g = ServiceLocator::LocateGlobals();
// DON'T RemoveRenderEngine, as that requires the engine list lock, and this
// is usually being triggered on a paint operation, when the lock is already
// owned by the paint.
// Instead we're releasing the Engine here. A pointer to it has already been
// given to the Renderer, so we don't want the unique_ptr to delete it. The
// Renderer will own its lifetime now.
_pVtRenderEngine.release();
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr);
_ShutdownIfNeeded();
}
void VtIo::_ShutdownIfNeeded()
{
// The callers should have both accquired the _shutdownLock at this point -
// we dont want a race on who is actually responsible for closing it.
if (_objectsCreated && _pVtInputThread == nullptr && _pVtRenderEngine == nullptr)
{
// At this point, we no longer have a renderer or inthread. So we've
// effectively been disconnected from the terminal.
// If we have any remaining attached processes, this will prepare us to send a ctrl+close to them
// if we don't, this will cause us to rundown and exit.
CloseConsoleProcessState();
// If we haven't terminated by now, that's because there's a client that's still attached.
// Force the handling of the control events by the attached clients.
// As of MSFT:19419231, CloseConsoleProcessState will make sure this
// happens if this method is called outside of lock, but if we're
// currently locked, we want to make sure ctrl events are handled
// _before_ we RundownAndExit.
ProcessCtrlEvents();
// Make sure we terminate.
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE);
}
}
// Method Description:
// - Tell the vt renderer to begin a resize operation. During a resize
// operation, the vt renderer should _not_ request to be repainted during a
@ -431,3 +429,28 @@ void VtIo::EndResize()
_pVtRenderEngine->EndResizeRequest();
}
}
// Method Description:
// - Called when the last client process exits. We'll use this to shut off the main IO thread.
// Arguments:
// - <none>
// Return Value:
// - <none>
void VtIo::_OnLastProcessExit()
{
// If a shutdown is signaled because one of the PTY pipes has broken connection with the
// hosting Terminal on top and we've been notified that the last client application has just
// disconnected, then we want to stop all IO channel communication.
//
// Normally IO channel communication stops itself when all outstanding references to the console
// session are broken with the driver, but in this circumstance, the Terminal application may still
// be holding a server, reference, or other handle to the session. It is implied that breaking any
// one of the connections or calling ClosePseudoConsole is a specific request to close all those things off from the
// Terminal's end. That means those handles are not really valid means of keeping the session open and therefore
// once the client connections are gone, there's no longer a reason to stick around even if the Terminal on top
// didn't fully clean up all of its handles before reaching this state.
if (_shutdownEvent.is_signaled())
{
ServiceLocator::LocateGlobals().pDeviceComm->Shutdown();
}
}

View file

@ -4,7 +4,6 @@
#pragma once
#include "..\inc\VtIoModes.hpp"
#include "..\inc\ITerminalOwner.hpp"
#include "..\renderer\vt\vtrenderer.hpp"
#include "VtInputThread.hpp"
#include "PtySignalInputThread.hpp"
@ -13,11 +12,11 @@ class ConsoleArguments;
namespace Microsoft::Console::VirtualTerminal
{
class VtIo : public Microsoft::Console::ITerminalOwner
class VtIo
{
public:
VtIo();
virtual ~VtIo() override = default;
~VtIo();
[[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs);
@ -33,13 +32,13 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] HRESULT SuppressResizeRepaint();
[[nodiscard]] HRESULT SetCursorPosition(const COORD coordCursor);
void CloseInput() override;
void CloseOutput() override;
void BeginResize();
void EndResize();
private:
wil::shared_event _shutdownEvent;
std::future<void> _shutdownWatchdog;
// After CreateIoHandlers is called, these will be invalid.
wil::unique_hfile _hInput;
wil::unique_hfile _hOutput;
@ -51,7 +50,6 @@ namespace Microsoft::Console::VirtualTerminal
bool _objectsCreated;
bool _lookingForCursorPosition;
std::mutex _shutdownLock;
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
@ -59,9 +57,10 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, const std::wstring& VtMode, _In_opt_ const HANDLE SignalHandle);
void _ShutdownIfNeeded();
void _OnLastProcessExit();
#ifdef UNIT_TESTING
bool _doNotTerminate;
friend class VtIoTests;
#endif
};

View file

@ -0,0 +1,244 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
using WEX::Logging::Log;
using WEX::TestExecution::TestData;
using namespace WEX::Common;
class PtyTests
{
BEGIN_TEST_CLASS(PtyTests)
END_TEST_CLASS()
HRESULT SpawnClient(HPCON hPC, wil::unique_process_information& pi)
{
HRESULT hr = E_FAIL;
STARTUPINFOEXW startupInfo{};
if (S_OK == (hr = InitializeStartupInfoAttachedToPseudoConsole(&startupInfo, hPC)))
{
// Launch cmd to emit some text back via the pipe
auto szCommand = _wcsdup(L"cmd.exe");
hr = CreateProcessW(
NULL,
szCommand,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&startupInfo.StartupInfo,
&pi) ?
S_OK :
GetLastError();
free(szCommand);
}
return hr;
}
struct Baton
{
HPCON hPC;
HANDLE ev;
DWORD closeMethod;
HANDLE inputWriter;
HANDLE outputReader;
};
HRESULT RunTest(bool inherit, bool read, bool write, DWORD endSessionBy)
{
Log::Comment(L"Creating communication pipes.");
HANDLE h1i, h1o, h2i, h2o;
auto f = !!CreatePipe(&h1o, &h1i, nullptr, 0);
if (!f)
{
Log::Comment(L"Beefed it at pipe 1");
return 1;
}
f = !!CreatePipe(&h2o, &h2i, nullptr, 0);
if (!f)
{
Log::Comment(L"Beefed it at pipe 2");
return 1;
}
DWORD dwFlags = 0;
if (inherit)
{
Log::Comment(L"Setting inherit flag...");
dwFlags = PSEUDOCONSOLE_INHERIT_CURSOR;
}
Log::Comment(L"Calling CreatePseudoConsole");
HPCON hPC;
auto hr = CreatePseudoConsole({ 80, 25 }, h1o, h2i, dwFlags, &hPC);
if (hr != S_OK)
{
Log::Comment(String().Format(L"Failed: %8.08x", hr));
return 1;
}
Log::Comment(L"Closing my half of the communication pipes.");
CloseHandle(h1o);
CloseHandle(h2i);
if (write)
{
// We do this out of order (writing the answer before we are asked) because we're doing it single threaded.
Log::Comment(L"Writing cursor response into buffer before we're asked.");
const char* buffer = "\x1b[0;0R";
DWORD dwWritten = 0;
WriteFile(h1i, buffer, 6, &dwWritten, nullptr);
const auto glewrite = GetLastError();
}
wil::unique_process_information pi;
Log::Comment(L"Spawning client application.");
hr = SpawnClient(hPC, pi);
if (read)
{
Log::Comment(L"Reading the cursor request from the buffer so it will be drained.");
byte bufferOut[256];
DWORD dwRead = 0;
ReadFile(h2o, &bufferOut, ARRAYSIZE(bufferOut), &dwRead, nullptr);
}
if (hr != S_OK)
{
Log::Comment(String().Format(L"Spawn took a trip to beeftown: %8.08x", hr));
return 1;
}
Log::Comment(L"Letting CMD actually spawn?");
Sleep(1000); // Let it settle?
Baton b{ hPC, nullptr };
b.ev = CreateEventW(nullptr, TRUE, FALSE, L"It is an event");
b.closeMethod = endSessionBy;
b.inputWriter = h1i;
b.outputReader = h2o;
CreateThread(
nullptr, 0, [](LPVOID c) -> DWORD {
Baton& b = *reinterpret_cast<Baton*>(c);
Log::Comment(L"Closing?");
switch (b.closeMethod)
{
case 0:
Log::Comment(L"Closing with the API (breaks signal handle)");
ClosePseudoConsole(b.hPC);
break;
case 1:
Log::Comment(L"Closing by breaking input handle.");
CloseHandle(b.inputWriter);
break;
case 2:
Log::Comment(L"Closing by breaking output handle.");
CloseHandle(b.outputReader);
break;
}
SetEvent(b.ev);
return 0;
},
(LPVOID)&b,
0,
nullptr);
Log::Comment(L"Waiting to let the environment teardown.");
auto r = WaitForSingleObject(b.ev, 5000);
switch (r)
{
case WAIT_OBJECT_0:
Log::Comment(L"Hey look it works.");
break;
case WAIT_TIMEOUT:
Log::Comment(L"\x1b[4;1;31mYOU DEADLOCKED IT\x1b[m\n");
return HRESULT_FROM_WIN32(WAIT_TIMEOUT);
case WAIT_FAILED:
{
auto gle = GetLastError();
Log::Comment(String().Format(L"You somehow broke it even worse (GLE=%d)", gle));
return HRESULT_FROM_WIN32(gle);
}
}
return S_OK;
}
// Initializes the specified startup info struct with the required properties and
// updates its thread attribute list with the specified ConPTY handle
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEXW* pStartupInfo, HPCON hPC)
{
HRESULT hr{ E_UNEXPECTED };
if (pStartupInfo)
{
size_t attrListSize{};
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEXW);
// Get the size of the thread attribute list.
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
// Allocate a thread attribute list of the correct size
pStartupInfo->lpAttributeList =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
// Initialize thread attribute list
if (pStartupInfo->lpAttributeList && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize))
{
// Set Pseudo Console attribute
hr = UpdateProcThreadAttribute(
pStartupInfo->lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL) ?
S_OK :
HRESULT_FROM_WIN32(GetLastError());
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
TEST_METHOD(PtyInitAndShutdown)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:inheritCursor", L"{true, false}")
TEST_METHOD_PROPERTY(L"Data:readOutput", L"{true, false}")
TEST_METHOD_PROPERTY(L"Data:writeInput", L"{true, false}")
TEST_METHOD_PROPERTY(L"Data:endSessionBy", L"{0, 1, 2}")
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD_PROPERTIES()
bool inheritCursor;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"inheritCursor", inheritCursor));
bool readOutput;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"readOutput", readOutput));
bool writeInput;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"writeInput", writeInput));
DWORD endSessionBy;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"endSessionBy", endSessionBy));
VERIFY_SUCCEEDED(RunTest(inheritCursor, readOutput, writeInput, endSessionBy));
}
};

View file

@ -12,6 +12,7 @@
<ClCompile Include="API_InputTests.cpp" />
<ClCompile Include="API_ModeTests.cpp" />
<ClCompile Include="API_OutputTests.cpp" />
<ClCompile Include="API_PtyTests.cpp" />
<ClCompile Include="API_RgbColorTests.cpp" />
<ClCompile Include="API_TitleTests.cpp" />
<ClCompile Include="API_PolicyTests.cpp" />

View file

@ -84,6 +84,9 @@
<ClCompile Include="API_FillOutputTests.cpp">
<Filter>Source Files\API</Filter>
</ClCompile>
<ClCompile Include="API_PtyTests.cpp">
<Filter>Source Files\API</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Common.hpp">

View file

@ -36,6 +36,7 @@ SOURCES = \
API_RgbColorTests.cpp \
API_TitleTests.cpp \
API_PolicyTests.cpp \
API_PtyTests.cpp \
CJK_DbcsTests.cpp \
Message_KeyPressTests.cpp \
DefaultResource.rc # Autogenerated file name + version for Device Guard whitelisting effort

View file

@ -56,7 +56,8 @@ public:
ULONG cursorPixelWidth = 1;
NTSTATUS ntstatusConsoleInputInitStatus;
wil::unique_event_nothrow hConsoleInputInitEvent;
wil::unique_event_nothrow consoleInputSetupEvent;
wil::unique_event_nothrow consoleInputInitializedEvent;
DWORD dwInputThreadId;
std::vector<wchar_t> WordDelimiters;

View file

@ -499,10 +499,21 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
WriteToScreen(screenInfo, screenInfo.GetViewport());
}
// Routine Description:
// - Dispatches final close event to connected clients or will run down and exit (and never return) if all clients
// are already gone.
// - NOTE: MUST BE CALLED UNDER LOCK. We don't want clients joining or leaving while we're telling them to close and running down.
// Arguments:
// - <none>
// Return Value:
// - <none>
// TODO: MSFT 9450717 This should join the ProcessList class when CtrlEvents become moved into the server. https://osgvsowi/9450717
void CloseConsoleProcessState()
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
FAIL_FAST_IF(!(gci.IsConsoleLocked()));
// If there are no connected processes, sending control events is pointless as there's no one do send them to. In
// this case we'll just exit conhost.
@ -514,14 +525,4 @@ void CloseConsoleProcessState()
}
HandleCtrlEvent(CTRL_CLOSE_EVENT);
// Jiggle the handle: (see MSFT:19419231)
// When we call this function, we'll only actually close the console once
// we're totally unlocked. If our caller has the console locked, great,
// we'll displatch the ctrl event once they unlock. However, if they're
// not running under lock (eg PtySignalInputThread::_GetData), then the
// ctrl event will never actually get dispatched.
// So, lock and unlock here, to make sure the ctrl event gets handled.
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
}

View file

@ -249,7 +249,8 @@ void ConsoleCheckDebug()
{
auto& g = ServiceLocator::LocateGlobals();
RETURN_IF_FAILED(ConsoleServerInitialization(Server, args));
RETURN_IF_FAILED(g.hConsoleInputInitEvent.create(wil::EventOptions::None));
RETURN_IF_FAILED(g.consoleInputSetupEvent.create(wil::EventOptions::ManualReset));
RETURN_IF_FAILED(g.consoleInputInitializedEvent.create(wil::EventOptions::ManualReset));
// Set up and tell the driver about the input available event.
RETURN_IF_FAILED(g.hInputEvent.create(wil::EventOptions::ManualReset));
@ -552,13 +553,20 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
{
ServiceLocator::LocateGlobals().dwInputThreadId = pNewThread->GetThreadId();
// The ConsoleInputThread needs to lock the console so we must first unlock it ourselves.
UnlockConsole();
g.hConsoleInputInitEvent.wait();
LockConsole();
// The ConsoleInputThread needs to perform things under lock,
// but if we unlock it, we don't know who will get the lock.
// So we will signal that it is safe for the other thread to do its work as
// we hold the lock on its behalf and wait for it to tell us that it is done.
g.consoleInputSetupEvent.SetEvent();
g.consoleInputInitializedEvent.wait();
CloseHandle(Thread);
g.hConsoleInputInitEvent.release();
// OK, we've been told that the input thread is done initializing under lock.
// Cleanup the handles and events we used to maintain our virtual lock passing dance.
CloseHandle(Thread); // This doesn't stop the thread from running.
g.consoleInputInitializedEvent.release();
g.consoleInputSetupEvent.release();
if (!NT_SUCCESS(g.ntstatusConsoleInputInitStatus))
{
@ -652,7 +660,8 @@ DWORD WINAPI ConsoleIoThread(LPVOID /*lpParameter*/)
HRESULT hr = ServiceLocator::LocateGlobals().pDeviceComm->ReadIo(ReplyMsg, &ReceiveMsg);
if (FAILED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED))
if (hr == HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED) ||
hr == E_APPLICATION_EXITING)
{
fShouldExit = true;

View file

@ -23,6 +23,20 @@ class Microsoft::Console::VirtualTerminal::VtIoTests
{
TEST_CLASS(VtIoTests);
wil::shared_event _shutdownEvent;
TEST_CLASS_SETUP(VtIoTestsSetup)
{
_shutdownEvent.create(wil::EventOptions::ManualReset);
return true;
}
TEST_METHOD_SETUP(VtIoMethodSetup)
{
_shutdownEvent.ResetEvent();
return true;
}
// General Tests:
TEST_METHOD(NoOpStartTest);
TEST_METHOD(ModeParsingTest);
@ -119,28 +133,28 @@ void VtIoTests::DtorTestJustEngine()
wil::unique_hfile hOutputFile;
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderer256 = new Xterm256Engine(std::move(hOutputFile), p, SetUpViewport(), colorTable, colorTableSize);
auto pRenderer256 = new Xterm256Engine(std::move(hOutputFile), _shutdownEvent, p, SetUpViewport(), colorTable, colorTableSize);
Log::Comment(NoThrowString().Format(L"Made Xterm256Engine"));
delete pRenderer256;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderEngineXterm = new XtermEngine(std::move(hOutputFile), p, SetUpViewport(), colorTable, colorTableSize, false);
auto pRenderEngineXterm = new XtermEngine(std::move(hOutputFile), _shutdownEvent, p, SetUpViewport(), colorTable, colorTableSize, false);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete pRenderEngineXterm;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderEngineXtermAscii = new XtermEngine(std::move(hOutputFile), p, SetUpViewport(), colorTable, colorTableSize, true);
auto pRenderEngineXtermAscii = new XtermEngine(std::move(hOutputFile), _shutdownEvent, p, SetUpViewport(), colorTable, colorTableSize, true);
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
delete pRenderEngineXtermAscii;
Log::Comment(NoThrowString().Format(L"Deleted."));
hOutputFile.reset(INVALID_HANDLE_VALUE);
auto pRenderEngineWinTelnet = new WinTelnetEngine(std::move(hOutputFile), p, SetUpViewport(), colorTable, colorTableSize);
auto pRenderEngineWinTelnet = new WinTelnetEngine(std::move(hOutputFile), _shutdownEvent, p, SetUpViewport(), colorTable, colorTableSize);
Log::Comment(NoThrowString().Format(L"Made WinTelnetEngine"));
delete pRenderEngineWinTelnet;
Log::Comment(NoThrowString().Format(L"Deleted."));
@ -173,6 +187,7 @@ void VtIoTests::DtorTestDeleteVtio()
VtIo* vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -185,6 +200,7 @@ void VtIoTests::DtorTestDeleteVtio()
vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -198,6 +214,7 @@ void VtIoTests::DtorTestDeleteVtio()
vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -211,6 +228,7 @@ void VtIoTests::DtorTestDeleteVtio()
vtio = new VtIo();
Log::Comment(NoThrowString().Format(L"Made VtIo"));
vtio->_pVtRenderEngine = std::make_unique<WinTelnetEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -246,6 +264,7 @@ void VtIoTests::DtorTestStackAlloc()
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -256,6 +275,7 @@ void VtIoTests::DtorTestStackAlloc()
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -267,6 +287,7 @@ void VtIoTests::DtorTestStackAlloc()
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -278,6 +299,7 @@ void VtIoTests::DtorTestStackAlloc()
{
VtIo vtio;
vtio._pVtRenderEngine = std::make_unique<WinTelnetEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -310,6 +332,7 @@ void VtIoTests::DtorTestStackAllocMany()
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio1;
vtio1._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -318,6 +341,7 @@ void VtIoTests::DtorTestStackAllocMany()
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio2;
vtio2._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -327,6 +351,7 @@ void VtIoTests::DtorTestStackAllocMany()
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio3;
vtio3._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -336,6 +361,7 @@ void VtIoTests::DtorTestStackAllocMany()
hOutputFile.reset(INVALID_HANDLE_VALUE);
VtIo vtio4;
vtio4._pVtRenderEngine = std::make_unique<WinTelnetEngine>(std::move(hOutputFile),
_shutdownEvent,
p,
SetUpViewport(),
colorTable,
@ -415,6 +441,7 @@ void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest()
Log::Comment(L"\tinitializing vtio");
VtIo vtio;
vtio._doNotTerminate = true; // suspend process termination on teardown so we don't lose the test host process
VERIFY_IS_FALSE(vtio.IsUsingVt());
VERIFY_ARE_EQUAL(nullptr, vtio._pPtySignalInputThread);
VERIFY_SUCCEEDED(vtio._Initialize(inPipeReadSide.release(), outPipeWriteSide.release(), L"", signalPipeReadSide.release()));

View file

@ -59,6 +59,8 @@ class Microsoft::Console::Render::VtRendererTest
{
TEST_CLASS(VtRendererTest);
wil::shared_event _shutdownEvent;
TEST_CLASS_SETUP(ClassSetup)
{
// clang-format off
@ -79,6 +81,9 @@ class Microsoft::Console::Render::VtRendererTest
g_ColorTable[14] = RGB(249, 241, 165); // Bright Yellow
g_ColorTable[15] = RGB(242, 242, 242); // White
// clang-format on
_shutdownEvent.create(wil::EventOptions::ManualReset);
return true;
}
@ -87,6 +92,12 @@ class Microsoft::Console::Render::VtRendererTest
return true;
}
TEST_METHOD_SETUP(MethodSetup)
{
_shutdownEvent.ResetEvent();
return true;
}
// Defining a TEST_METHOD_CLEANUP seemed to break x86 test pass. Not sure why,
// something about the clipboard tests and
// YOU_CAN_ONLY_DESIGNATE_ONE_CLASS_METHOD_TO_BE_A_TEST_METHOD_SETUP_METHOD
@ -197,7 +208,7 @@ void VtRendererTest::TestPaintXterm(XtermEngine& engine, std::function<void()> p
void VtRendererTest::VtSequenceHelperTests()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -254,7 +265,7 @@ void VtRendererTest::VtSequenceHelperTests()
void VtRendererTest::Xterm256TestInvalidate()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -389,7 +400,7 @@ void VtRendererTest::Xterm256TestInvalidate()
void VtRendererTest::Xterm256TestColors()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -482,7 +493,7 @@ void VtRendererTest::Xterm256TestColors()
void VtRendererTest::Xterm256TestCursor()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -593,7 +604,7 @@ void VtRendererTest::Xterm256TestCursor()
void VtRendererTest::XtermTestInvalidate()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -727,7 +738,7 @@ void VtRendererTest::XtermTestInvalidate()
void VtRendererTest::XtermTestColors()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -789,7 +800,7 @@ void VtRendererTest::XtermTestColors()
void VtRendererTest::XtermTestCursor()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
std::unique_ptr<XtermEngine> engine = std::make_unique<XtermEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE), false);
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -900,7 +911,7 @@ void VtRendererTest::XtermTestCursor()
void VtRendererTest::WinTelnetTestInvalidate()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -972,7 +983,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
void VtRendererTest::WinTelnetTestColors()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -1026,7 +1037,7 @@ void VtRendererTest::WinTelnetTestColors()
void VtRendererTest::WinTelnetTestCursor()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<WinTelnetEngine> engine = std::make_unique<WinTelnetEngine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -1106,7 +1117,7 @@ void VtRendererTest::WinTelnetTestCursor()
void VtRendererTest::TestWrapping()
{
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
std::unique_ptr<Xterm256Engine> engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, SetUpViewport(), g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);
@ -1159,7 +1170,7 @@ void VtRendererTest::TestResize()
{
Viewport view = SetUpViewport();
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
auto engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, view, g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto engine = std::make_unique<Xterm256Engine>(std::move(hFile), _shutdownEvent, p, view, g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
engine->SetTestCallback(pfn);

View file

@ -1,33 +0,0 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
Module Name:
- ITerminalOwner.hpp
Abstract:
- Provides an abstraction for Closing the Input/Output objects of a terminal
connection. This is implemented by VtIo in the host, and is used by the
renderer to be able to tell the VtIo object that the renderer has had it's
pipe broken.
Author(s):
- Mike Griese (migrie) 28 March 2018
--*/
#pragma once
namespace Microsoft::Console
{
class ITerminalOwner
{
public:
virtual ~ITerminalOwner() = 0;
virtual void CloseInput() = 0;
virtual void CloseOutput() = 0;
};
// See docs/virtual-dtors.md for an explanation of why this is weird.
inline Microsoft::Console::ITerminalOwner::~ITerminalOwner() {}
}

View file

@ -21,6 +21,7 @@
#include <algorithm>
#include <atomic>
#include <deque>
#include <future>
#include <list>
#include <memory>
#include <map>

View file

@ -61,7 +61,6 @@ ULONG ConvertMouseButtonState(_In_ ULONG Flag, _In_ ULONG State)
VOID SetConsoleWindowOwner(const HWND hwnd, _Inout_opt_ ConsoleProcessHandle* pProcessData)
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
FAIL_FAST_IF(!(gci.IsConsoleLocked()));
DWORD dwProcessId;
DWORD dwThreadId;
@ -994,7 +993,10 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
{
InitEnvironmentVariables();
LockConsole();
// When the setup event is triggered, the I/O thread has told us that it is
// officially holding the global lock on our behalf and we're free to setup.
ServiceLocator::LocateGlobals().consoleInputSetupEvent.wait();
HHOOK hhook = nullptr;
NTSTATUS Status = STATUS_SUCCESS;
@ -1013,16 +1015,18 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
ServiceLocator::LocatePseudoWindow();
}
UnlockConsole();
// Now we must pass back virtual ownership of the global lock by telling the I/O
// thread that we're done and what our status is.
ServiceLocator::LocateGlobals().ntstatusConsoleInputInitStatus = Status;
ServiceLocator::LocateGlobals().consoleInputInitializedEvent.SetEvent();
// If not successful, end the thread by returning the status.
// If successful, proceed down below to the message pump loop.
if (!NT_SUCCESS(Status))
{
ServiceLocator::LocateGlobals().ntstatusConsoleInputInitStatus = Status;
ServiceLocator::LocateGlobals().hConsoleInputInitEvent.SetEvent();
return Status;
}
ServiceLocator::LocateGlobals().hConsoleInputInitEvent.SetEvent();
for (;;)
{
MSG msg;

View file

@ -68,7 +68,32 @@ using namespace Microsoft::Console::Types;
LRESULT Status = 0;
BOOL Unlock = TRUE;
LockConsole();
// If we're during the initial allocation of the console, then the I/O thread is servicing a
// Console Connection Request under lock as the input thread is created on the first connection to a client.
// Normally we'd take the lock here at the top of the window proc to ensure consistency while servicing messages,
// but because the I/O thread needs to hold the lock for the duration of
// servicing the connection request, we have a wink-nudge agreement here with the I/O thread about the lock.
// As long as the input setup event exists and is signaled, we've been told that the I/O thread has the lock
// and is waiting for us to respond.
// As long as the input initialized event exists and is unsignaled, we haven't told the I/O thread that we're done
// yet and it can resume use of the lock.
// So under these conditions where we are assured the I/O thread is holding the lock on our behalf and waiting for us...
// skip the lock/unlock behavior down this entire procedure and perform the messages needed to get things set up
// knowing we have exclusive reign over the console variables.
if (g.consoleInputSetupEvent && g.consoleInputSetupEvent.is_signaled() &&
g.consoleInputInitializedEvent && !g.consoleInputInitializedEvent.is_signaled())
{
Unlock = FALSE;
}
// Under normal conditions, Unlock is TRUE at the top and we will need to take the lock
// to process messages.
// Under special init conditions, someone else holds the lock for us and we can skip all lock/unlock behavior
// by checking the Unlock variable.
if (Unlock)
{
LockConsole();
}
SCREEN_INFORMATION& ScreenInfo = GetScreenInfo();
if (hWnd == nullptr) // TODO: this might not be possible anymore
@ -83,7 +108,10 @@ using namespace Microsoft::Console::Types;
Status = DefWindowProcW(hWnd, Message, wParam, lParam);
}
UnlockConsole();
if (Unlock)
{
UnlockConsole();
}
return Status;
}
@ -214,7 +242,10 @@ using namespace Microsoft::Console::Types;
pSuggestionSize->cy = RECT_HEIGHT(&rectProposed);
// Format our final suggestion for consumption.
UnlockConsole();
if (Unlock)
{
UnlockConsole();
}
return TRUE;
}
@ -485,8 +516,11 @@ using namespace Microsoft::Console::Types;
{
HMENU hHeirMenu = Menu::s_GetHeirMenuHandle();
Unlock = FALSE;
UnlockConsole();
if (Unlock)
{
Unlock = FALSE;
UnlockConsole();
}
TrackPopupMenuEx(hHeirMenu,
TPM_RIGHTBUTTON | (GetSystemMetrics(SM_MENUDROPALIGNMENT) == 0 ? TPM_LEFTALIGN : TPM_RIGHTALIGN),
@ -508,8 +542,11 @@ using namespace Microsoft::Console::Types;
switch (wParam & 0x00FF)
{
case HTCAPTION:
UnlockConsole();
Unlock = FALSE;
if (Unlock)
{
UnlockConsole();
Unlock = FALSE;
}
SetActiveWindow(hWnd);
SendMessageTimeoutW(hWnd, WM_SYSCOMMAND, SC_MOVE | wParam, lParam, SMTO_NORMAL, INFINITE, nullptr);
break;
@ -675,8 +712,11 @@ using namespace Microsoft::Console::Types;
case CM_BEEP:
{
UnlockConsole();
Unlock = FALSE;
if (Unlock)
{
UnlockConsole();
Unlock = FALSE;
}
// Don't fall back to Beep() on win32 systems -- if the user configures their system for no sound, we should
// respect that.

View file

@ -10,11 +10,12 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
WinTelnetEngine::WinTelnetEngine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const IDefaultColorProvider& colorProvider,
const Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,
const WORD cColorTable) :
VtEngine(std::move(hPipe), colorProvider, initialViewport),
VtEngine(std::move(hPipe), shutdownEvent, colorProvider, initialViewport),
_ColorTable(ColorTable),
_cColorTable(cColorTable)
{

View file

@ -24,6 +24,7 @@ namespace Microsoft::Console::Render
{
public:
WinTelnetEngine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const Microsoft::Console::IDefaultColorProvider& colorProvider,
const Microsoft::Console::Types::Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,

View file

@ -9,11 +9,12 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const IDefaultColorProvider& colorProvider,
const Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,
const WORD cColorTable) :
XtermEngine(std::move(hPipe), colorProvider, initialViewport, ColorTable, cColorTable, false)
XtermEngine(std::move(hPipe), shutdownEvent, colorProvider, initialViewport, ColorTable, cColorTable, false)
{
}

View file

@ -24,6 +24,7 @@ namespace Microsoft::Console::Render
{
public:
Xterm256Engine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const Microsoft::Console::IDefaultColorProvider& colorProvider,
const Microsoft::Console::Types::Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,

View file

@ -10,12 +10,13 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const IDefaultColorProvider& colorProvider,
const Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,
const WORD cColorTable,
const bool fUseAsciiOnly) :
VtEngine(std::move(hPipe), colorProvider, initialViewport),
VtEngine(std::move(hPipe), shutdownEvent, colorProvider, initialViewport),
_ColorTable(ColorTable),
_cColorTable(cColorTable),
_fUseAsciiOnly(fUseAsciiOnly),

View file

@ -28,6 +28,7 @@ namespace Microsoft::Console::Render
{
public:
XtermEngine(_In_ wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const Microsoft::Console::IDefaultColorProvider& colorProvider,
const Microsoft::Console::Types::Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,

View file

@ -20,7 +20,7 @@ using namespace Microsoft::Console::Types;
// HRESULT error code if painting didn't start successfully.
[[nodiscard]] HRESULT VtEngine::StartPaint() noexcept
{
if (_pipeBroken)
if (_shutdownEvent.is_signaled())
{
return S_FALSE;
}

View file

@ -25,10 +25,12 @@ const COORD VtEngine::INVALID_COORDS = { -1, -1 };
// - <none>
// Return Value:
// - An instance of a Renderer.
VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
VtEngine::VtEngine(wil::unique_hfile pipe,
wil::shared_event shutdownEvent,
const IDefaultColorProvider& colorProvider,
const Viewport initialViewport) :
RenderEngineBase(),
_shutdownEvent(shutdownEvent),
_hFile(std::move(pipe)),
_colorProvider(colorProvider),
_LastFG(INVALID_COLOR),
@ -49,9 +51,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_circled(false),
_firstPaint(true),
_skipCursor(false),
_pipeBroken(false),
_exitResult{ S_OK },
_terminalOwner{ nullptr },
_newBottomLine{ false },
_deferredCursorPos{ INVALID_COORDS },
_inResizeRequest{ false },
@ -60,10 +59,42 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
#ifndef UNIT_TESTING
// When unit testing, we can instantiate a VtEngine without a pipe.
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
THROW_HR_IF(E_HANDLE, !_shutdownEvent);
#else
// member is only defined when UNIT_TESTING is.
_usingTestCallback = false;
#endif
// Set up a background thread to wait until the shared shutdown event is called and then execute cleanup tasks.
_shutdownWatchdog = std::async(std::launch::async, [=] {
_shutdownEvent.wait();
// When someone calls the _Flush method, they will go into a potentially blocking WriteFile operation.
// Before they do that, they'll store their thread ID here so we can get them unstuck should we be shutting down.
if (const auto threadId = _blockedThreadId.load())
{
// If we indeed had a valid thread ID meaning someone is blocked on a WriteFile operation,
// then let's open a handle to their thread. We need the standard read/write privileges to see
// what their thread is up to (it will not work without these) and also the Terminate privilege to
// unstick them.
wil::unique_handle threadHandle(OpenThread(STANDARD_RIGHTS_ALL | THREAD_TERMINATE, FALSE, threadId));
LOG_LAST_ERROR_IF_NULL(threadHandle.get());
if (threadHandle)
{
// Presuming we got all the way to acquiring the blocked thread's handle, call the OS function that
// will unstick any thread that is otherwise permanently blocked on a synchronous operation.
LOG_IF_WIN32_BOOL_FALSE(CancelSynchronousIo(threadHandle.get()));
}
}
});
}
VtEngine::~VtEngine()
{
if (_shutdownEvent)
{
_shutdownEvent.SetEvent();
}
}
// Method Description:
@ -104,19 +135,20 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
}
#endif
if (!_pipeBroken)
if (!_shutdownEvent.is_signaled())
{
// Stash the current thread ID before we go into the potentially blocking synchronous write file operation.
// This will let the shutdown watchdog thread break us out of the stuck state should a shutdown event
// occur while we're still waiting for the WriteFile to complete.
_blockedThreadId.store(GetCurrentThreadId());
bool fSuccess = !!WriteFile(_hFile.get(), _buffer.data(), static_cast<DWORD>(_buffer.size()), nullptr, nullptr);
_blockedThreadId.store(0); // When done, clear the thread ID.
_buffer.clear();
if (!fSuccess)
{
_exitResult = HRESULT_FROM_WIN32(GetLastError());
_pipeBroken = true;
if (_terminalOwner)
{
_terminalOwner->CloseOutput();
}
return _exitResult;
_shutdownEvent.SetEvent();
RETURN_LAST_ERROR();
}
}
@ -399,11 +431,6 @@ bool VtEngine::_AllIsInvalid() const
return S_OK;
}
void VtEngine::SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner)
{
_terminalOwner = terminalOwner;
}
// Method Description:
// - sends a sequence to request the end terminal to tell us the
// cursor position. The terminal will reply back on the vt input handle.

View file

@ -18,7 +18,6 @@ Author(s):
#include "../inc/RenderEngineBase.hpp"
#include "../../inc/IDefaultColorProvider.hpp"
#include "../../inc/ITerminalOutputConnection.hpp"
#include "../../inc/ITerminalOwner.hpp"
#include "../../types/inc/Viewport.hpp"
#include "tracing.hpp"
#include <string>
@ -33,11 +32,12 @@ namespace Microsoft::Console::Render
static const size_t ERASE_CHARACTER_STRING_LENGTH = 8;
static const COORD INVALID_COORDS;
VtEngine(_In_ wil::unique_hfile hPipe,
VtEngine(wil::unique_hfile hPipe,
wil::shared_event shutdownEvent,
const Microsoft::Console::IDefaultColorProvider& colorProvider,
const Microsoft::Console::Types::Viewport initialViewport);
virtual ~VtEngine() override = default;
~VtEngine() override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
@ -93,11 +93,14 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring& str) noexcept = 0;
void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner);
void BeginResizeRequest();
void EndResizeRequest();
protected:
wil::shared_event _shutdownEvent;
std::future<void> _shutdownWatchdog;
std::atomic<DWORD> _blockedThreadId;
wil::unique_hfile _hFile;
std::string _buffer;
@ -129,10 +132,6 @@ namespace Microsoft::Console::Render
bool _newBottomLine;
COORD _deferredCursorPos;
bool _pipeBroken;
HRESULT _exitResult;
Microsoft::Console::ITerminalOwner* _terminalOwner;
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
bool _inResizeRequest{ false };

View file

@ -5,7 +5,8 @@
#include "DeviceComm.h"
DeviceComm::DeviceComm(_In_ HANDLE Server) :
_Server(Server)
_Server(Server),
_shutdown(false)
{
THROW_HR_IF(E_HANDLE, Server == INVALID_HANDLE_VALUE);
}
@ -14,6 +15,13 @@ DeviceComm::~DeviceComm()
{
}
// Routine Description:
// - Intentionally shut down our communications channel
void DeviceComm::Shutdown()
{
_shutdown = true;
}
// Routine Description:
// - Needs to be called once per server session and typically as the absolute first operation.
// - This sets up the driver with the input event that it will need to coordinate with when client
@ -41,6 +49,17 @@ DeviceComm::~DeviceComm()
[[nodiscard]] HRESULT DeviceComm::ReadIo(_In_opt_ PCONSOLE_API_MSG const pReplyMsg,
_Out_ CONSOLE_API_MSG* const pMessage) const
{
// If we've been told to shutdown, we should allow a reply to go through to finish things off,
// but no more reads of new messages are allowed.
if (_shutdown)
{
if (pReplyMsg)
{
LOG_IF_FAILED(CompleteIo(&pReplyMsg->Complete));
}
return E_APPLICATION_EXITING;
}
HRESULT hr = _CallIoctl(IOCTL_CONDRV_READ_IO,
pReplyMsg == nullptr ? nullptr : &pReplyMsg->Complete,
pReplyMsg == nullptr ? 0 : sizeof(pReplyMsg->Complete),

View file

@ -36,6 +36,8 @@ public:
[[nodiscard]] HRESULT AllowUIAccess() const;
void Shutdown();
private:
[[nodiscard]] HRESULT _CallIoctl(_In_ DWORD dwIoControlCode,
_In_reads_bytes_opt_(cbInBufferSize) PVOID pInBuffer,
@ -44,4 +46,5 @@ private:
_In_ DWORD cbOutBufferSize) const;
wil::unique_handle _Server;
bool _shutdown;
};

View file

@ -92,6 +92,9 @@ void ConsoleProcessList::FreeProcessData(_In_ ConsoleProcessHandle* const pProce
_processes.remove(pProcessData);
// Attempt to dispatch events to registered listeners.
_NotifyOnLastFree();
delete pProcessData;
}
@ -329,6 +332,19 @@ bool ConsoleProcessList::IsEmpty() const
return _processes.empty();
}
// Routine Description:
// - Gives us an event that we should notify when the last process is removed from this list
// - NOTE: This is a function callback and not an event so the notification of state can occur under the
// same global lock state that is required to add/remove client processes from the list.
// Arguments:
// - func - A function to call while we're removing the last process.
// Return Value:
// - <none>
void ConsoleProcessList::RegisterForNotifyOnLastFree(std::function<void()> func)
{
_notifyOnLastFree.emplace_back(func);
}
// Routine Description:
// - Requests the OS allow the console to set one of its child processes as the foreground window
// Arguments:
@ -340,3 +356,20 @@ void ConsoleProcessList::_ModifyProcessForegroundRights(const HANDLE hProcess, c
{
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(hProcess, fForeground));
}
// Routine Description:
// - Attempts to notify anyone listening for when the last client process is disconnecting.
// Arguments:
// - <none>
// Return Value:
// - <none>
void ConsoleProcessList::_NotifyOnLastFree() const
{
if (_processes.empty())
{
for (auto& func : _notifyOnLastFree)
{
func();
}
}
}

View file

@ -58,8 +58,13 @@ public:
bool IsEmpty() const;
void RegisterForNotifyOnLastFree(std::function<void()> func);
private:
std::list<ConsoleProcessHandle*> _processes;
std::vector<std::function<void()>> _notifyOnLastFree;
void _NotifyOnLastFree() const;
void _ModifyProcessForegroundRights(const HANDLE hProcess, const bool fForeground) const;
};