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

Avoid async infinite loop on tab close #15335

Merged
merged 4 commits into from
Aug 15, 2023
Merged

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented May 11, 2023

When a tab gets closed, _RemoveTab will call TabBase::Shutdown(),
which then re-raises the Closed event, which will end up calling
_RemoveTab again, etc. The only reason this didn't crash WT so far
is because _RemoveOnCloseRoutine contains a resume_foreground,
which would resolve the recursion and turn it into CPU usage.
It would spin as long as WinUI hasn't discard the tab object,
which takes an unpredictable amount of time.

Raising the Closed event from Shutdown() is unnecessary, because
the handlers of the event end up calling _RemoveTab anyways.
Technically the entire Closed event can be removed now, but I left it
in anyways because resolving the architectural "knot" around the way
tab closing after the last pane closes is implemented requires much
more significant changes.

This commit additionally removes the _createCloseLock mutex in Pane
as it was very likely not working as intended anyways. Only some methods
were protected by it and it doesn't avoid any STA/MTA/NA issues either.

Validation Steps Performed

  • Closing tabs and panes always ends up calling Shutdown()

Closes #14898

@lhecker lhecker added Issue-Bug It either shouldn't be doing this or needs an investigation. Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) Product-Terminal The new Windows Terminal. labels May 11, 2023
Comment on lines +515 to +518
if (tab == _settingsTab)
{
_settingsTab = nullptr;
}
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 felt like this is a more robust way to express the same idea of "there's just one instance of settings and closing it destroys it".

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

What about when a tab closes itself, like when the terminal connection inside it exits? That does not come in on the main dispatcher thread, therefore interacting with UI elements during the Close callback is a danger.

Are we certain that Close always comes in on the right thread?

@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 12, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 12, 2023
@@ -1843,135 +1842,128 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
closedChild->_ClosedByParentHandlers();
}

winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
void Pane::_CloseChildRoutine(const bool closeFirst)
Copy link
Member Author

Choose a reason for hiding this comment

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

You need to view this changeset with whitespace changes suppressed. :)

@lhecker
Copy link
Member Author

lhecker commented May 12, 2023

Are we certain that Close always comes in on the right thread?

You're right, it didn't. In my original PR it worked, because I also made it so that the close event gets emitted on the UI thread. I now brought those changes into this PR as well, but unfortunately it increases the diff size. It's easier to view with whitespace changes suppressed.

@@ -683,8 +683,6 @@ bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
return false;
}

std::unique_lock lock{ _createCloseLock };
Copy link
Member

Choose a reason for hiding this comment

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

Do you know what this was guarding and how we are now safe without it?

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

I think I understand why this works. Thanks!

1.18 material?

@DHowett
Copy link
Member

DHowett commented May 15, 2023

Oh, you removed it from 1.18. OK

@lhecker
Copy link
Member Author

lhecker commented May 15, 2023

Yeah I think I'd like to wait with this until 1.19... There's still these comments in the code and I'd like to 110% grasp what they mean in practice and if it was a practical concern to begin with:

// It's possible that this event handler started being executed, then before
// we got the lock, another thread created another child. So our control is
// actually no longer _our_ control, and instead could be a descendant.

...because to be honest I don't think any of this worked the way we imagined it would. I'm pretty sure all of that _IsLeaf() code is unneeded and always has been.

Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

This all seems good. The only thing I want to double check with you on is why we're removing the lock from...

  • SwapPanes()
  • _CloseTerminalRequestedHandler()
  • Shutdown()
  • _CloseChild()
  • _CloseChildRoutine()

I get that's the point of this PR haha, but why is it safe to do that now?

@lhecker
Copy link
Member Author

lhecker commented Jun 22, 2023

@carlos-zamora Sorry for the late response. As far as I can tell, _ControlConnectionStateChangedHandler is the only function that is being called from a background thread. It would end up calling Close() which I assume is why the mutex was called _createCloseLock. I now added a resume_foreground there so it's been fixed (hopefully).

The reason I removed it is simply because that mutex provides a false sense of security. We can't use it to make shutdown secure, when Pane has ~50 member functions and only 6 of them are thread safe. 😅 It just doesn't work at all and never did. 😅😅

@zadjii-msft
Copy link
Member

This has ✅✅, but never merged while I was out. I haven't read it yet. Should we include this in the bug bash?

@lhecker
Copy link
Member Author

lhecker commented Aug 8, 2023

Yes, please. I've merged in main just now to resolve the conflict.

@zadjii-msft zadjii-msft enabled auto-merge (squash) August 15, 2023 18:03
@zadjii-msft zadjii-msft merged commit 3701d0e into main Aug 15, 2023
15 of 17 checks passed
@zadjii-msft zadjii-msft deleted the dev/lhecker/pane-infinite-loop branch August 15, 2023 19:12
zadjii-msft added a commit that referenced this pull request Oct 2, 2023
Well, Pane doesn't _only_ care if the connection isn't entering a terminal
state. It does need to update its own state first.

Regressed in #15335

Closes #16068
DHowett pushed a commit that referenced this pull request Oct 3, 2023
Well, Pane doesn't _only_ care if the connection isn't entering a
terminal state. It does need to update its own state first.

Regressed in #15335

Closes #16068
DHowett pushed a commit that referenced this pull request Oct 3, 2023
Well, Pane doesn't _only_ care if the connection isn't entering a
terminal state. It does need to update its own state first.

Regressed in #15335

Closes #16068

(cherry picked from commit 4145f18)
Service-Card-Id: 90731934
Service-Version: 1.19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Terminal The new Windows Terminal. zBugBash-Consider
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Terminal hangs when exiting a debug tap session
4 participants