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

Fixed self capture in TermControl #3908

Merged
5 commits merged into from
Dec 13, 2019
Merged

Conversation

mkitzan
Copy link
Contributor

@mkitzan mkitzan commented Dec 11, 2019

Summary of the Pull Request

An asynchronous event handler capturing raw this in TermControl was causing an exception to be thrown when a scroll update event occurred after closing the active tab. This PR replaces all non-auto_revoke lambda captures in TermControl to capture (and validate) a winrt::weak_ref instead of using raw this.

References

PR Checklist

Detailed Description of the Pull Request / Additional comments

TermControl is already a WinRT type so no changes were required to enable the winrt::weak_ref functionality. There was only one strange change I had to make. In the destructor's helper function Close, I had to remove two calls to Stop which were throwing under some circumstances. Fortunately, these calls don't appear to be critical, but definitely a spot to look into when reviewing this PR.

Beyond scrolling, any anomalous crash related to the following functionality while closing a tab or WT may be fixed by this PR:

  • Settings updating
  • Changing background color

Validation Steps Performed

Before these changes I was able to consistently repro the issue in #2947. Now, I can no longer repro the issue.

@DHowett-MSFT
Copy link
Contributor

Thanks! For this one, actually, I’m wondering if we should use C++/WinRT’s auto_revoke machinery. Since we are largely dealing with other WinRT objects and not manually bound std::functions, we can probably use more of the cool platform accordances.

These auto_revokers get torn down during destruction, which ensures that a destructed TermControl has all of its handlers unbound before it becomes invalid.

@DHowett-MSFT
Copy link
Contributor

(There are a couple where we explicitly specify the tear down order, but those shouldn’t need to be weak.)

@mkitzan
Copy link
Contributor Author

mkitzan commented Dec 11, 2019

I'll start by rolling back the auto_revoke handlers that I changed to use weak_ref (didn't realize auto_revoke made the event safe), and if I see some easy wins for the others I'll rework them to.

@DHowett-MSFT
Copy link
Contributor

Thanks 😄

WinRT has some neat features
@mkitzan
Copy link
Contributor Author

mkitzan commented Dec 11, 2019

Was able to make one extra event auto_revoke, so all events TermControl owns are auto_revoke. The only real blocker to making the rest auto_revoke is that the XAML Controls in TermControl are using CoreDispatcher.RunAsync for their handler binding.

@mkitzan
Copy link
Contributor Author

mkitzan commented Dec 11, 2019

Azp unit testing got caught up again.
homer

@zadjii-msft
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@mkitzan mkitzan marked this pull request as ready for review December 11, 2019 18:32
Copy link
Member

@miniksa miniksa left a comment

Choose a reason for hiding this comment

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

I'm OK with this. Thanks.

src/cascadia/TerminalControl/TermControl.cpp Show resolved Hide resolved
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
// Update our control settings
_ApplyUISettings();
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you can [weakThis = get_weak()] if the weak ref is only going to be used in the lambda

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That form reads better. I'll include it in the next commit.

if (auto control{ weakThis.get() })
{
// Update our control settings
control->_ApplyUISettings();
Copy link
Contributor

Choose a reason for hiding this comment

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

not 100% sure on how i feel about indirecting through control-> every time; in debug builds each one will be a call to winrt::com_ptr::operator->()...
but i do not know how much, if at all, I care. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

(we could capture weakThis,this and then only use this implicitly if the weak got locked. @zadjii-msft?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer the way the weakThis,this lambdas read. All the control-> just clutters things, not mention your comment about winrt::com_ptr::operator->(). WinRT could almost use a class to support this form of lambda. It would be extensible to all the places where we're having trouble with the { this, pointer-to-member } event format. Anyways, I'll include the fix to these in the next revision commit.

Copy link
Member

Choose a reason for hiding this comment

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

I think I prefer it like this - not capturing this at all makes it harder for us to accidentally do the wrong thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess there's a "convenient" middle ground...

if (auto strongThis{ weakThis.get() })
{
    auto& that{ *strongThis };
    that->x;
    that->y;
}

I am worried about the constant indirection through operator->... but at the same time, WRL::ComPtr literally is that for all com calls, sooooooooo maybe i should not be worried?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's just that this is a member function, but we have to write it in a weird way that makes it not look like a member function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me know which form to go with, and I'll include it in the next commit.

localAutoScrollTimer.Stop();
// _autoScrollTimer timer, now stopped, is destroyed.
}

Copy link
Contributor

Choose a reason for hiding this comment

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

i'm surprised tearing down the timers isn't necessary anymore, but I'm willing to believe it 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those Stop calls were only throwing in the event that TermControl was being destructed at the end of an event handler, which was strange. Could just wrap them in try-catch blocks if the Stop calls really should be there 🤔

Copy link
Member

Choose a reason for hiding this comment

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

The most correct solution might be to make both the timers Tick events take a get_weak() as opposed to this, right? If we had done that from the start, we probably wouldn't have ever needed these lines...

@mkitzan
Copy link
Contributor Author

mkitzan commented Dec 11, 2019

Thanks for the reviews. I've pushed some changes.

@DHowett-MSFT
Copy link
Contributor

I'm closing and reopening to convince the CLA bot.

Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I'm okay with this, but maybe we should make those Tick events take get_weak(). @mkitzan are you planning on making a pass for all the { this, &TermControl::EventHandler } instances?

localAutoScrollTimer.Stop();
// _autoScrollTimer timer, now stopped, is destroyed.
}

Copy link
Member

Choose a reason for hiding this comment

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

The most correct solution might be to make both the timers Tick events take a get_weak() as opposed to this, right? If we had done that from the start, we probably wouldn't have ever needed these lines...

@mkitzan
Copy link
Contributor Author

mkitzan commented Dec 12, 2019

Yeah, get_weak for Tick makes sense. I wasn't originally planning on making changes to the existing { this, &TermControl::EventHandler }. However, it would be an easy fix to convert these to { get_weak(), &TermControl::EventHandler }.

@zadjii-msft zadjii-msft added the AutoMerge Marked for automatic merge by the bot when requirements are met label Dec 13, 2019
@ghost
Copy link

ghost commented Dec 13, 2019

Hello @zadjii-msft!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit a3f3cc8 into microsoft:master Dec 13, 2019
@mkitzan mkitzan deleted the fix-termcontrol-lambdas branch December 14, 2019 01:35
@ghost
Copy link

ghost commented Jan 14, 2020

🎉Windows Terminal Preview v0.8.10091.0 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AutoMerge Marked for automatic merge by the bot when requirements are met
Projects
None yet
4 participants