-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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 potential lags/deadlocks during tab close #14041
Changes from 3 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 |
---|---|---|
|
@@ -39,14 +39,29 @@ void ServiceLocator::SetOneCoreTeardownFunction(void (*pfn)()) noexcept | |
s_oneCoreTeardownFunction = pfn; | ||
} | ||
|
||
[[noreturn]] void ServiceLocator::RundownAndExit(const HRESULT hr) | ||
void ServiceLocator::RundownAndExit(const HRESULT hr) | ||
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. Changes here are scary. Are we sure this won't cause the OneCore console to regress? 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. No I'm not sure, but I don't believe so. The only effective difference is the addition of the |
||
{ | ||
static thread_local bool preventRecursion = false; | ||
static std::atomic<bool> locked; | ||
|
||
// BODGY: | ||
// pRender->TriggerTeardown() might cause another VtEngine pass, which then might fail to write to the IO pipe. | ||
// If that happens it calls VtIo::CloseOutput(), which in turn calls ServiceLocator::RundownAndExit(). | ||
// This prevents the unintended recursion and resulting deadlock. | ||
if (std::exchange(preventRecursion, true)) | ||
{ | ||
return; | ||
} | ||
|
||
// MSFT:40146639 | ||
// The premise of this function is that 1 thread enters and 0 threads leave alive. | ||
// We need to prevent anyone from calling us until we actually ExitProcess(), | ||
// so that we don't TriggerTeardown() twice. LockConsole() can't be used here, | ||
// because doing so would prevent the render thread from progressing. | ||
AcquireSRWLockExclusive(&s_shutdownLock); | ||
if (locked.exchange(true, std::memory_order_relaxed)) | ||
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. Is this safe on all architectures? Is relaxed correct? 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. Yes it's safe on all architectures. Atomic operations themselves are always exactly as atomic as any other, no matter what memory order is specified. Relaxed only means that no additional memory is synchronized/flushed (which is fine here). |
||
{ | ||
Sleep(INFINITE); | ||
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. So just to confirm. If we get here, it is the first entry and we take the lock, we proceed .We might cause either...
The first one is fixed by What if we trigger another thread to 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. No the change to 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. Please put a big ol' comment here why it's ok to put 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. It ended up being a bit of a shorter comment. But I think that one, plus the longer explanation in front of the |
||
} | ||
|
||
// MSFT:15506250 | ||
// In VT I/O Mode, a client application might die before we've rendered | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,7 @@ namespace Microsoft::Console::Interactivity | |
public: | ||
static void SetOneCoreTeardownFunction(void (*pfn)()) noexcept; | ||
|
||
[[noreturn]] static void RundownAndExit(const HRESULT hr); | ||
static void RundownAndExit(const HRESULT hr); | ||
|
||
// N.B.: Location methods without corresponding creation methods | ||
// automatically create the singleton object on demand. | ||
|
@@ -86,7 +86,6 @@ namespace Microsoft::Console::Interactivity | |
|
||
static HWND LocatePseudoWindow(const HWND owner = nullptr /*HWND_DESKTOP = 0*/); | ||
|
||
protected: | ||
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. Why remove protected? Should we keep protected for some of these? 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 class is |
||
ServiceLocator(const ServiceLocator&) = delete; | ||
ServiceLocator& operator=(const ServiceLocator&) = delete; | ||
|
||
|
@@ -112,7 +111,5 @@ namespace Microsoft::Console::Interactivity | |
static Globals s_globals; | ||
static bool s_pseudoWindowInitialized; | ||
static wil::unique_hwnd s_pseudoWindow; | ||
|
||
static inline SRWLOCK s_shutdownLock = SRWLOCK_INIT; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,10 +84,10 @@ void ConPtyTests::CreateConPtyNoPipes() | |
VERIFY_FAILED(_CreatePseudoConsole(defaultSize, nullptr, nullptr, 0, &pcon)); | ||
|
||
VERIFY_SUCCEEDED(_CreatePseudoConsole(defaultSize, nullptr, goodOut, 0, &pcon)); | ||
_ClosePseudoConsoleMembers(&pcon); | ||
_ClosePseudoConsoleMembers(&pcon, TRUE); | ||
|
||
VERIFY_SUCCEEDED(_CreatePseudoConsole(defaultSize, goodIn, nullptr, 0, &pcon)); | ||
_ClosePseudoConsoleMembers(&pcon); | ||
_ClosePseudoConsoleMembers(&pcon, TRUE); | ||
} | ||
|
||
void ConPtyTests::CreateConPtyBadSize() | ||
|
@@ -131,7 +131,7 @@ void ConPtyTests::GoodCreate() | |
&pcon)); | ||
|
||
auto closePty = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pcon); | ||
_ClosePseudoConsoleMembers(&pcon, TRUE); | ||
}); | ||
} | ||
|
||
|
@@ -160,7 +160,7 @@ void ConPtyTests::GoodCreateMultiple() | |
0, | ||
&pcon1)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pcon1); | ||
_ClosePseudoConsoleMembers(&pcon1, TRUE); | ||
}); | ||
|
||
VERIFY_SUCCEEDED( | ||
|
@@ -170,7 +170,7 @@ void ConPtyTests::GoodCreateMultiple() | |
0, | ||
&pcon2)); | ||
auto closePty2 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pcon2); | ||
_ClosePseudoConsoleMembers(&pcon2, TRUE); | ||
}); | ||
} | ||
|
||
|
@@ -197,7 +197,7 @@ void ConPtyTests::SurvivesOnBreakInput() | |
0, | ||
&pty)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pty); | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
}); | ||
|
||
DWORD dwExit; | ||
|
@@ -242,7 +242,7 @@ void ConPtyTests::SurvivesOnBreakOutput() | |
0, | ||
&pty)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pty); | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
}); | ||
|
||
DWORD dwExit; | ||
|
@@ -287,7 +287,7 @@ void ConPtyTests::DiesOnBreakBoth() | |
0, | ||
&pty)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pty); | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
}); | ||
|
||
DWORD dwExit; | ||
|
@@ -358,7 +358,7 @@ void ConPtyTests::DiesOnClose() | |
0, | ||
&pty)); | ||
auto closePty1 = wil::scope_exit([&] { | ||
_ClosePseudoConsoleMembers(&pty); | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
}); | ||
|
||
DWORD dwExit; | ||
|
@@ -382,7 +382,7 @@ void ConPtyTests::DiesOnClose() | |
Log::Comment(NoThrowString().Format(L"Sleep a bit to let the process attach")); | ||
Sleep(100); | ||
|
||
_ClosePseudoConsoleMembers(&pty); | ||
_ClosePseudoConsoleMembers(&pty, TRUE); | ||
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. Y'all don't like default params, huh? :( 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. It's a C function - that's why it can't have that. 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. Also FWIW no, I personally don't like default parameters! I also don't like underdocumented boolean parameters for reasons I've mentioned before, but since this is an internal/in-file interface I don't care as much. |
||
|
||
GetExitCodeProcess(hConPtyProcess.get(), &dwExit); | ||
VERIFY_ARE_NOT_EQUAL(dwExit, (DWORD)STILL_ACTIVE); | ||
|
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.
Are we certain that we'll never make it here while a connection is closing? As long as we never transition a connection into the Failed state after we transition it into Closing...
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.
That's actually entirely possible and the source of the race condition that I fixed.
While
_isStateAtOrBeyond
itself might be atomic, that doesn't mean it protects us from other threads changing the state under us concurrently. Unless I misunderstood your question, I don't believe that this is any more or less correct than before. (But the change is required to make theCancelSynchronousIo
handling work correctly.)