-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Fix a deadlock during ConPTY shutdown #14160
Changes from all commits
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 |
---|---|---|
|
@@ -143,6 +143,8 @@ VtIo::VtIo() : | |
auto& globals = ServiceLocator::LocateGlobals(); | ||
|
||
const auto& gci = globals.getConsoleInformation(); | ||
// SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine. | ||
assert(gci.IsConsoleLocked()); | ||
|
||
try | ||
{ | ||
|
@@ -337,21 +339,20 @@ void VtIo::CreatePseudoWindow() | |
|
||
void VtIo::SetWindowVisibility(bool showOrHide) noexcept | ||
{ | ||
// MSFT:40853556 Grab the shutdown lock here, so that another | ||
// thread can't trigger a CloseOutput and release the | ||
// _pVtRenderEngine out from underneath us. | ||
std::lock_guard<std::mutex> lk(_shutdownLock); | ||
auto& gci = ::Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); | ||
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. Actually, i am suddenly worried this is a deadlock factory. We call 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. The previous code already acquired the console lock here. All I did was move the if condition inside the locked area. If we have such a bug then we should have had it before this PR is/was merged. |
||
|
||
gci.LockConsole(); | ||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); | ||
|
||
// ConsoleInputThreadProcWin32 calls VtIo::CreatePseudoWindow, | ||
// which calls CreateWindowExW, which causes a WM_SIZE message. | ||
// In short, this function might be called before _pVtRenderEngine exists. | ||
// See PtySignalInputThread::CreatePseudoWindow(). | ||
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. since the window message loop happens on a different thread, what happens if we get a window message for visibility while we are shutting down? we will come in here, lock console, and stall... right? 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. The only function that blocks is |
||
if (!_pVtRenderEngine) | ||
{ | ||
return; | ||
} | ||
|
||
auto& gci = ::Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); | ||
|
||
gci.LockConsole(); | ||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); | ||
|
||
LOG_IF_FAILED(_pVtRenderEngine->SetWindowVisibility(showOrHide)); | ||
} | ||
|
||
|
@@ -444,56 +445,36 @@ void VtIo::SetWindowVisibility(bool showOrHide) noexcept | |
|
||
void VtIo::CloseInput() | ||
{ | ||
// This will release the lock when it goes out of scope | ||
std::lock_guard<std::mutex> lk(_shutdownLock); | ||
_pVtInputThread = nullptr; | ||
_ShutdownIfNeeded(); | ||
_shutdownNow(); | ||
} | ||
|
||
void VtIo::CloseOutput() | ||
{ | ||
// This will release the lock when it goes out of scope | ||
std::lock_guard<std::mutex> lk(_shutdownLock); | ||
|
||
auto& 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() | ||
void VtIo::_shutdownNow() | ||
{ | ||
// The callers should have both acquired 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. | ||
LockConsole(); | ||
ProcessCtrlEvents(); | ||
|
||
// Make sure we terminate. | ||
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE); | ||
} | ||
// At this point, we no longer have a renderer or inthread. So we've | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// effectively been disconnected from the terminal. | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// 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. | ||
LockConsole(); | ||
ProcessCtrlEvents(); | ||
|
||
// Make sure we terminate. | ||
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE); | ||
} | ||
|
||
// Method Description: | ||
|
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.
FYI
_InputThread
is now[[noreturn]]
becauseCloseInput
is[[noreturn]]
. Hence, noreturn
is needed, despite this function "returning" aDWORD
.