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 issue with exception being thrown during cleanup of NavigationView. #6797

Merged
merged 3 commits into from
Mar 12, 2022

Conversation

StephenLPeters
Copy link
Contributor

@StephenLPeters StephenLPeters commented Mar 8, 2022

When NavigationView is being cleaned up in needs to detatch the event handlers on its children NVI's that are still alive, however if those NVI's have already been cleaned up they can be in a state that SetValue(nullptr) throws an exception, which are not allowed during the NavigationView's destructor.

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Mar 8, 2022
@StephenLPeters StephenLPeters reopened this Mar 8, 2022
@StephenLPeters StephenLPeters marked this pull request as draft March 8, 2022 00:22
@StephenLPeters
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kmahone
Copy link
Member

kmahone commented Mar 8, 2022

I am curious: Why is there a need for 's_NavigationViewItemRevokersProperty'? Why can the revokers not be stored in a std::vector on NavigationView?

@StephenLPeters
Copy link
Contributor Author

I am curious: Why is there a need for 's_NavigationViewItemRevokersProperty'? Why can the revokers not be stored in a std::vector on NavigationView?

I think the original idea is that by attaching these revokers to the NVI the lifetime of the revoker objects would be tied to the NVI and we can set it and forget it. This was the case for multiple years until we identified a problem, the this pointer in the handler could be invalid if the NVI outlives the NavigationView. So we updated this to track the NVI's that have revoker objects set on them so we can clear them when we shut down. Looking closer at this there are definite improvements we can make...

I see three strategies

  1. keep the current model, but replace the this pointer in the handlers with a weak_ref, this should make it so that when the NVI outlives the NV we don't have an invalid raw pointer. Then we can remove all of the tracking.
  2. Keep the current model, and when the navigation view is being destructed iterate over all NVI and revoke all of the attached objects. We don't need to track which nvi's have revoker objects on them. We can be sure they all do.
  3. We can stop attaching these revokers to the NVI itself, keeping them in a map of nvi to revokers objects and then everytime a nvi wants to get cleaned up we will need to revoke all the associated handlers.

@kmahone @lhecker thoughts?

RevokeNavigationViewItemRevokers(nvi);
nvi.SetValue(s_NavigationViewItemRevokersProperty, nullptr);
}
catch (...) {}
Copy link
Member

Choose a reason for hiding this comment

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

Do we know what was causing the exceptions in the first place?

Copy link
Member

Choose a reason for hiding this comment

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

I'm currently looking into it, because I'm now able to reproduce the issue. 🙂

@kmahone
Copy link
Member

kmahone commented Mar 8, 2022

I am curious: Why is there a need for 's_NavigationViewItemRevokersProperty'? Why can the revokers not be stored in a std::vector on NavigationView?

I think the original idea is that by attaching these revokers to the NVI the lifetime of the revoker objects would be tied to the NVI and we can set it and forget it. This was the case for multiple years until we identified a problem, the this pointer in the handler could be invalid if the NVI outlives the NavigationView. So we updated this to track the NVI's that have revoker objects set on them so we can clear them when we shut down. Looking closer at this there are definite improvements we can make...

I see three strategies

  1. keep the current model, but replace the this pointer in the handlers with a weak_ref, this should make it so that when the NVI outlives the NV we don't have an invalid raw pointer. Then we can remove all of the tracking.
  2. Keep the current model, and when the navigation view is being destructed iterate over all NVI and revoke all of the attached objects. We don't need to track which nvi's have revoker objects on them. We can be sure they all do.
  3. We can stop attaching these revokers to the NVI itself, keeping them in a map of nvi to revokers objects and then everytime a nvi wants to get cleaned up we will need to revoke all the associated handlers.

@kmahone @lhecker thoughts?

Would a std::vector<winrt::Foo_revoker> on NavView for each event (Tapped, KeyDown, etc.) be sufficient? Or even a std::vector<NavigationViewItemRevokers>.

Once the NavView is being destroyed, the std::vector would be destroyed and the revokers would get revoked.

Is the problem with this approach that the NavView would keep the NVIs alive even if they are removed from the NavView? Does a winrt::Foo_revoker keep a strong ref to the source element? I'm not sure.

@StephenLPeters
Copy link
Contributor Author

Would a std::vector<winrt::Foo_revoker> on NavView for each event (Tapped, KeyDown, etc.) be sufficient? Or even a std::vector<NavigationViewItemRevokers>.

Once the NavView is being destroyed, the std::vector would be destroyed and the revokers would get revoked.

Is the problem with this approach that the NavView would keep the NVIs alive even if they are removed from the NavView? Does a winrt::Foo_revoker keep a strong ref to the source element? I'm not sure.

The problem with this approach is that the revoker object being stored in NavigationView will keep the DependencyPropertyChangedCallback object alive even after the associated NVI has been cleaned up, that is until the NavigationView is destructed.

@StephenLPeters
Copy link
Contributor Author

I think... the event source has a strong reference to the DependencyPropertyChangedCallback object.. does the revoker object? it might only have a weak ref...

@kmahone
Copy link
Member

kmahone commented Mar 8, 2022

Would a std::vector<winrt::Foo_revoker> on NavView for each event (Tapped, KeyDown, etc.) be sufficient? Or even a std::vector<NavigationViewItemRevokers>.
Once the NavView is being destroyed, the std::vector would be destroyed and the revokers would get revoked.
Is the problem with this approach that the NavView would keep the NVIs alive even if they are removed from the NavView? Does a winrt::Foo_revoker keep a strong ref to the source element? I'm not sure.

The problem with this approach is that the revoker object being stored in NavigationView will keep the DependencyPropertyChangedCallback object alive even after the associated NVI has been cleaned up, that is until the NavigationView is destructed.

And why is that a problem? Is it because when we then try to revoke the event from the now destroyed NVI we crash?

@StephenLPeters
Copy link
Contributor Author

It would result in memory growth as the app remains open. If the NavigationView is the top level UI and never gets cleaned up except as a (as it is designed for) then as items get added and removed from the navigation view the application memory would grow.

@StephenLPeters
Copy link
Contributor Author

Confrimed that the revoker object (usually, and in this case always) has a weak ref to the callback object, so in this case the only down side of this method would be that the vector of revokers will grow for the lifetime of the navigationview.

@StephenLPeters StephenLPeters added area-NavigationView NavView control team-Controls Issue for the Controls team labels Mar 11, 2022
@StephenLPeters StephenLPeters changed the title Attempt to fix issue with exception being thrown during cleanup. Fix issue with exception being thrown during cleanup of NavigationView. Mar 11, 2022
@StephenLPeters StephenLPeters marked this pull request as ready for review March 11, 2022 00:46
@StephenLPeters
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

{
for (const auto& nvi : m_itemsWithRevokerObjects)
{
nvi.SetValue(s_NavigationViewItemRevokersProperty, nullptr);
try
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment explaining why this is necessary.

@StephenLPeters
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@StephenLPeters StephenLPeters merged commit d8d4f4f into main Mar 12, 2022
@StephenLPeters StephenLPeters deleted the user/stpete/NavigationViewItemRevokers branch March 12, 2022 16:24
@ojhad ojhad removed the needs-triage Issue needs to be triaged by the area owners label Mar 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-NavigationView NavView control team-Controls Issue for the Controls team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants