Skip to content
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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Feb 26, 2025

I found two issues:

  • winrt::event doesn't extend the lifetime of its owning struct, nor does it block during revocation until pending calls have completed (= destruction of a callee can occur while it's being called).
  • Lifetime management in ControlCore is bad in general (because it's unsafe), but the OutputIdle handling in particular was just wrong.

(Hopefully) Closes #18598

Validation Steps Performed

  • Can't repro the original failure ❌
  • Opening and closing tabs as fast as possible doesn't crash anymore ✅

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Bug It either shouldn't be doing this or needs an investigation. Area-Quality Stability, Performance, Etc. Product-Terminal The new Windows Terminal. Severity-Crash Crashes are real bad news. labels Feb 26, 2025
@lhecker lhecker force-pushed the dev/lhecker/18598-shutdown-crash branch from 5302525 to 45d2519 Compare February 26, 2025 16:15
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler });
_stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) {
StateChanged.raise(*this, nullptr);
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { get_weak(), &DebugTapConnection::_OutputHandler });
Copy link
Member Author

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 without this being deallocated.

Copy link
Member

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

Copy link
Member Author

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.

_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 });
Copy link
Member Author

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 without this being deallocated. (Same below.)

// 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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(For future code archealogists.)

Comment on lines -145 to -151
// 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();
Copy link
Member Author

Choose a reason for hiding this comment

The 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 RenderThread into the Renderer. No more ownership issues.

Comment on lines 230 to 232

_renderer.reset();
_renderEngine.reset();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this, because I fixed the member order of the class.

{
AddRenderEngine(rgpEngines[i]);
}
THROW_IF_FAILED(_pThread->Initialize(this));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always forget... is this guaranteed to be usable in the constructor? is Initialize safe for this if this is partially constructed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a valid pointer in the constructor, it's just that the members aren't necessarily fully initialized yet.

@DHowett
Copy link
Member

DHowett commented Feb 26, 2025

I know the tests probably interact with RenderThread in weird ways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Quality Stability, Performance, Etc. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Terminal The new Windows Terminal. Severity-Crash Crashes are real bad news.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Microsoft::Console::VirtualTerminal::StateMachine::ProcessString Crash
2 participants