-
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 a shutdown race condition in ControlCore #18632
base: main
Are you sure you want to change the base?
Changes from 1 commit
45d2519
410b0ae
9ecf892
96d6a0c
1487b2a
01b1777
dc45cbb
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 |
---|---|---|
|
@@ -142,23 +142,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach | ||
// the UIA Engine to the renderer. This prevents us from signaling changes to the cursor or buffer. | ||
{ | ||
// First create the render thread. | ||
// Then stash a local pointer to the render thread so we can initialize it and enable it | ||
// to paint itself *after* we hand off its ownership to the renderer. | ||
// We split up construction and initialization of the render thread object this way | ||
// because the renderer and render thread have circular references to each other. | ||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>(); | ||
auto* const localPointerToThread = renderThread.get(); | ||
Comment on lines
-145
to
-151
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 has always been a thorn in my eye, so I fixed it in this PR by moving the |
||
|
||
// Now create the renderer and initialize the render thread. | ||
const auto& renderSettings = _terminal->GetRenderSettings(); | ||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get(), nullptr, 0, std::move(renderThread)); | ||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(renderSettings, _terminal.get()); | ||
|
||
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); }); | ||
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); }); | ||
_renderer->SetRendererEnteredErrorStateCallback([this]() { RendererEnteredErrorState.raise(nullptr, nullptr); }); | ||
|
||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); | ||
} | ||
|
||
UpdateSettings(settings, unfocusedAppearance); | ||
|
@@ -186,19 +176,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// thread is a workaround for us to hit GH#12607 less often. | ||
shared->outputIdle = std::make_unique<til::debounced_func_trailing<>>( | ||
std::chrono::milliseconds{ 100 }, | ||
[weakTerminal = std::weak_ptr{ _terminal }, weakThis = get_weak(), dispatcher = _dispatcher]() { | ||
[this, weakThis = get_weak(), dispatcher = _dispatcher]() { | ||
dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() { | ||
if (const auto self = weakThis.get(); self && !self->_IsClosing()) | ||
{ | ||
self->OutputIdle.raise(*self, nullptr); | ||
} | ||
}); | ||
|
||
if (const auto t = weakTerminal.lock()) | ||
{ | ||
const auto lock = t->LockForWriting(); | ||
t->UpdatePatternsUnderLock(); | ||
} | ||
// We can't use a weak-ref to `_terminal` here, because it takes significant | ||
// dependency on the lifetime of `this` (primarily our _renderer). | ||
// We can't use a weak-ref to `this` either, because if we end up being the last holder of a strong-ref | ||
// (after promotion to it), we'd drop ControlCore on a background thread, and it's not thread-safe. | ||
const auto lock = _terminal->LockForWriting(); | ||
_terminal->UpdatePatternsUnderLock(); | ||
}); | ||
|
||
// If you rapidly show/hide Windows Terminal, something about GotFocus()/LostFocus() gets broken. | ||
|
@@ -227,9 +218,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
ControlCore::~ControlCore() | ||
{ | ||
Close(); | ||
|
||
_renderer.reset(); | ||
_renderEngine.reset(); | ||
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. I removed this, because I fixed the member order of the class. |
||
} | ||
|
||
void ControlCore::Detach() | ||
|
@@ -276,16 +264,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
auto oldState = ConnectionState(); // rely on ControlCore's automatic null handling | ||
// revoke ALL old handlers immediately | ||
|
||
// TODO: This manual event revoking doesn't make much sense. | ||
// We could just drop the old connection. Why have all that Close() stuff? | ||
// It also shouldn't need to be exposed to the outside. I suspect we can only | ||
// improve this though, once drag/drop of tabs doesn't use "startup actions" anymore. | ||
Comment on lines
+270
to
+273
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. (For future code archealogists.) |
||
_connectionOutputEventRevoker.revoke(); | ||
_connectionStateChangedRevoker.revoke(); | ||
|
||
_connection = newConnection; | ||
if (_connection) | ||
{ | ||
// Subscribe to the connection's disconnected event and call our connection closed handlers. | ||
_connectionStateChangedRevoker = newConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) { | ||
ConnectionStateChanged.raise(*this, nullptr); | ||
}); | ||
_connectionStateChangedRevoker = newConnection.StateChanged(winrt::auto_revoke, { get_weak(), &ControlCore::_connectionStateChangedHandler }); | ||
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.
|
||
|
||
// Get our current size in rows/cols, and hook them up to | ||
// this connection too. | ||
|
@@ -303,8 +293,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
conpty.ReparentWindow(_owningHwnd); | ||
} | ||
|
||
// This event is explicitly revoked in the destructor: does not need weak_ref | ||
_connectionOutputEventRevoker = _connection.TerminalOutput(winrt::auto_revoke, { this, &ControlCore::_connectionOutputHandler }); | ||
_connectionOutputEventRevoker = _connection.TerminalOutput(winrt::auto_revoke, { get_weak(), &ControlCore::_connectionOutputHandler }); | ||
} | ||
|
||
// Fire off a connection state changed notification, to let our hosting | ||
|
@@ -2202,6 +2191,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
} | ||
} | ||
|
||
void ControlCore::_connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&) | ||
{ | ||
ConnectionStateChanged.raise(*this, nullptr); | ||
} | ||
|
||
::Microsoft::Console::Render::Renderer* ControlCore::GetRenderer() const noexcept | ||
{ | ||
return _renderer.get(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,22 +25,13 @@ static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 }; | |
// - Creates a new renderer controller for a console. | ||
// Arguments: | ||
// - pData - The interface to console data structures required for rendering | ||
// - pEngine - The output engine for targeting each rendering frame | ||
// Return Value: | ||
// - An instance of a Renderer. | ||
Renderer::Renderer(const RenderSettings& renderSettings, | ||
IRenderData* pData, | ||
_In_reads_(cEngines) IRenderEngine** const rgpEngines, | ||
const size_t cEngines, | ||
std::unique_ptr<RenderThread> thread) : | ||
Renderer::Renderer(const RenderSettings& renderSettings, IRenderData* pData) : | ||
_renderSettings(renderSettings), | ||
_pData(pData), | ||
_pThread{ std::move(thread) } | ||
_pData(pData) | ||
{ | ||
for (size_t i = 0; i < cEngines; i++) | ||
{ | ||
AddRenderEngine(rgpEngines[i]); | ||
} | ||
THROW_IF_FAILED(_pThread->Initialize(this)); | ||
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. I always forget... is 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.
|
||
} | ||
|
||
// Routine 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.
get_weak()
to ensure pending calls during revocation can complete withoutthis
being deallocated.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.
does this by chance fix the issue where closing a connection while the debug tap is on it crashes terminal
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 wasn't aware we had such an issue. It's quite likely that this fixes it.