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 TabItem memory leak #12418
Fix TabItem memory leak #12418
Conversation
You can test this PR using the following package version. |
The trouble with doing this when In the specific case of AlternativesWhen I start thinking about dynamically adding and removing tab items, I start questioning whether property makes sense at all. It's entirely legal for a single Perhaps it would be better to remove this property (or mark it as obsolete), then have templates instead bind to Another alternative is switching to logical tree attach/detach. These events are much more predictable. But are they raised when adding a tab item dynamically? A third option is to use a weak event subscription and let the garbage collector take care of things. |
Wait a second, the documentation for |
As I understand, weak reference points to the |
@TomEdwardsEnscape I agree with you on the point whether property makes sense at all. Looks like |
Managing subscriptions via logical tree should work |
Or we can move update logic to the TabStripPlacementProperty.Changed.AddClassHandler<TabControl>((x, args) => x.UpdateTabStripPlacement()); private void UpdateTabStripPlacement()
{
for (int i = 0; i < Items.Count; i++)
{
var control = ContainerFromIndex(i);
if (control is TabItem tabItem)
{
tabItem.TabStripPlacement = TabStripPlacement;
}
}
} And additionally call |
Placement is widely used for custom styles. (like menu like TabControl with items on left side with an indicator. ) |
I worked out where the strong reference is coming from. It's not from The class below avoids this, but still leaves The proper fix for this is a "weak subscribe" method that uses Moving the update logic to the protected void SubscribeToOwnerProperties(AvaloniaObject owner)
{
_ownerSubscriptions = owner.GetObservable(TabControl.TabStripPlacementProperty).Subscribe(new WeakObserver(this));
}
private class WeakObserver : IObserver<Dock>
{
private GCHandle _weakRef;
public WeakObserver(TabItem owner)
{
_weakRef = GCHandle.Alloc(owner, GCHandleType.Weak);
}
~WeakObserver()
{
_weakRef.Free();
}
public void OnCompleted()
{
if (_weakRef.Target is TabItem tabItem)
{
tabItem.TabStripPlacement = null;
}
_weakRef.Free();
GC.SuppressFinalize(this);
}
public void OnError(Exception error)
{
// ignore
}
public void OnNext(Dock value)
{
if (_weakRef.Target is TabItem tabItem)
{
tabItem.TabStripPlacement = value;
}
}
} |
@DmitryZhelnin Please update the PR to use this approach and we can merge |
c090644
to
367abbc
Compare
You can test this PR using the following package version. |
@Gillibald PR is updated. BTW I tried to run this test
and neither new version nor old one are passing. But if I take memory snapshots I can see that fixed version is working as intended. Are these tests out of date or I should fix it? |
83af3ea
to
bb150ea
Compare
b781017
to
3e0e9d4
Compare
src/Avalonia.Controls/TabControl.cs
Outdated
for (int i = 0; i < Items.Count; i++) | ||
{ | ||
var control = ContainerFromIndex(i); | ||
if (control is TabItem tabItem) | ||
{ | ||
tabItem.TabStripPlacement = TabStripPlacement; | ||
} | ||
} | ||
} |
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.
You can iterate over ItemsPresenterPart children instead, without calling ContainerFromIndex for each item. Which in theory can be virtualized too (==a lot of unnecessary iterations).
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.
Done
} | ||
} | ||
|
||
protected void SubscribeToOwnerProperties(AvaloniaObject owner) |
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.
Removal of this method is a breaking change. As noticed from the CI fail.
Can yo keep this method empty with Obsolete attribute? Just in case.
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.
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.
@maxkatz6 done
Head branch was pushed to by a user without write access
You can test this PR using the following package version. |
…ling ContainerFromIndex
0e89958
to
23023dd
Compare
You can test this PR using the following package version. |
* TabItem: dispose _ownerSubscriptions when item is detached from visual tree * TabItem: update of TabStripPlacement moved to TabControl * TabControl: iterate over ItemsPresenterPart's children instead of calling ContainerFromIndex * TabItem: SubscribeToOwnerProperties marked as obsolete --------- Co-authored-by: Dmitry Zhelnin <d.zhelnin@of-group.ru>
What does the pull request do?
Moves update of
TabStripPlacement
toTabContol
.What is the current behavior?
Subscription is not disposed and
TabItem
controls remain in memory.Fixed issues
Fixes #12416