-
Notifications
You must be signed in to change notification settings - Fork 668
viewModel memory leaks #8366
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
viewModel memory leaks #8366
Conversation
call them when models are removed or cleared
| if (currentWorkspaceViewModel == viewModel) | ||
| if(currentWorkspaceViewModel != null) | ||
| { | ||
| currentWorkspaceViewModel.Dispose(); |
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.
main entry point for disposal of viewModels - if we remove a workspace from the workspace collection - dispose it, this causes dispose to get called on nodeViewModel, NoteViewModel, PortViewModel, ConnectorViewModel, etc.
| public virtual void Dispose() | ||
| { | ||
| _model.PropertyChanged -= note_PropertyChanged; | ||
| DynamoSelection.Instance.Selection.CollectionChanged -= SelectionOnCollectionChanged; |
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.
should there be dispose on viewmodelbase covering few dispose (like selection)?
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.
I will give that a shot - I think the base implementation may be empty (nothing shared between all viewModels), but thats okay, it will be more clear.
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.
Possibly the base implementation should be abstract to force all derived classes to properly dispose their contents?
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.
still the base class can handle few common things?
|
@mjkkirschner @alfarok @ramramps @ColinDayOrg @smangarole What is the likely-hood of introducing bugs here? What should QA be on the lookout for? Custom Node workflows? |
| RunSettingsViewModel.PropertyChanged -= RunSettingsViewModel_PropertyChanged; | ||
| RunSettingsViewModel = null; | ||
| DynamoViewModel.Model.ShutdownStarted -= Model_ShutdownStarted; | ||
| //RunSettingsViewModel.Dispose(); |
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.
@mjkkirschner is this required or can it be removed?
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.
will remove.
|
@jnealb I would be most concerned about situations where viewModels are being disposed but are still being used by a view that is visible to the user - this should not happen but there could be edge cases - in that situation we would see the view no longer responding to events on the model. |
remove some comments
|
finding some more retention paths for |
|
|
||
| private void CloseHomeWorkspace(object parameter) | ||
| { | ||
| //TODO potentially dispose viewModel here. |
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.
Lately I have been adding "// TODO, QNTM-XXXX:" instead of just "// TODO" so that there is a connection between the todo and why it was created. It also makes it searchable by number. I'm not sure this is really necessary (or a good thing, though it seems good to me, opinions requested).
|
|
||
| public virtual void Dispose() | ||
| { | ||
| //TODO add other event removal here. |
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.
Same TODO comment here.
|
|
||
| DynamoViewModel.Model.PropertyChanged -= Model_PropertyChanged; | ||
| DynamoViewModel.Model.DebugSettings.PropertyChanged -= DebugSettings_PropertyChanged; | ||
| if (IsDebugBuild) |
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.
General question: Is the IsDebugBuild check necessary here? If an event is -= and the event had not been previously subscribed would this cause an issue? If not, it seems like it may possibly be safer just to unsubscribe it in case there is somewhere it is subscribed in the future outside of a IsDebugBuild check. No action necessary here, just asking a question.
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.
No, there should be no harm - unfortunately this call doesn't work in all cases - the engine controller gets recreated at some point when a new workspace is opened - sometimes - it is before this event is unsubscribed - that is bad.
I need to investigate this as well - thankfully this should not effect release builds.
| Model.ConnectorDeleted += Connectors_ConnectorDeleted; | ||
| Model.PropertyChanged += ModelPropertyChanged; | ||
|
|
||
| this.refreshViewHandler = (sender, e) => { RefreshViewOnSelectionChange(); }; |
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.
Maybe this should be a method instead of a lambda expression for clarity/continuity?
| } | ||
| _nodes.Clear(); | ||
| Errors.Clear(); | ||
| //TODO dispose the cleared errors? |
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.
Same TODO comment as above
|
Generally LGTM, with some comments. I'm not sure here is right place to address it, or even if there is a good answer, but I find it troubling that there is a pattern of subscribing to events in one place and unsubscribing from those events in another place, with no real connection between what was subscribed and what was unsubscribed. It seems like some kind of more comprehensive solution could be used like "UnsubscribeAllEvents" or something like that. |
watch3d viewmodel was not unsubscribing from methods on dynamo model - all watch3d view models leaked
…kspaces they are in.
| if (model3D != null) | ||
| { | ||
| model3D.Detach(); | ||
| model3D.Dispose(); |
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.
when we remove geometry from the background preview or watch3d node - actually dispose the tessellated objects, don't just detach them.
| protected override void Dispose(bool disposing) | ||
| { | ||
| base.Dispose(true); | ||
| UnregisterNodeEventHandlers(this.watchNode); |
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.
this is debugging code - will clean up.
| } | ||
|
|
||
| private void UnregisterEventHandlers() | ||
| protected void UnregisterEventHandlers() |
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.
these changes are debugging code - will clean up.
get rid of dispose call that does nothing
# Conflicts: # src/Libraries/Watch3DNodeModelsWpf/HelixWatch3DNodeViewModel.cs # src/Libraries/Watch3DNodeModelsWpf/Watch3DNodeViewCustomization.cs
|
@ColinDayOrg ready for a look, please see images in description. |
| if (Application.Current != null) | ||
| { | ||
| Application.Current.Deactivated += (s, args) => { OnRequestShowInCanvasSearch(ShowHideFlags.Hide); }; | ||
| Application.Current.Deactivated += currentApplicationDeactivated; |
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.
Not really a code review comment, but I think this type of change should be in the coding standards (i.e. no attaching lambda functions to event handlers, unless there is a specific reason).
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.
will add this to style guide.
|
Generally, I think that these changes will make the code base in a better state, so LGTM. That being said, I think that calling Dispose directly may have possible side effects, such as other objects having references to the objects that were disposed. (I am currently running into exactly this while working on a different issue). Possibly we may want to look into using SafeHandle (i.e. C# auto_ptr style handle). https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose Also, I'm not exactly sure what the images attached above are supposed to be showing me. I can see that they are different, but the memory used does not seem to be that different, the second image still has a ramp. |
|
We're kind of overloading the use of dispose here - most of the changes are are not related to unmanaged memory - do calling dispose does not actually destroy the object - it just removes references so that the object can be garbage collected. I do think that a better strategy would be good longterm or a guide for devs new to this repo. Theres definitely still leaks after this PR so there are ramp ups but the total memory saved is approximately 50 megs (not including unknown gpu memory saved) also it's not exactly a fair comparison as the memory pressure on my VM was less in the other test and so garbage collection was probably not occurring as frequently. |
|
so after this last commit (shot in the dark) the self serve - CI passes. - I will run it again though and see what happens. |
Purpose
HomeWorkspaceViewModelwas never being garbage collected - there were atleast 8 retention paths and in some cases 11. I found that almost all ViewModels were leaking and never being garbage collected.I added
Disposemethods to the viewModels which are called when the models are removed from their collections - for example, theNodeViewModelis disposed when its corresponding model is removed from theNodesCollection on theWorkspaceModel.These dispose methods cleanup event subscriptions that the viewModels had retaining them. These events point towards models, other viewModels, static instances etc...
Declarations
Check these if you believe they are true
*.resxfilespictures!
Please note that these runs show the difference between opening the graph⚠️ 😢 (machine is a VM with 512 mb of vram). This did not occur on this branch.
Geometry_Surfaces.dyn10 times in a row from the recent files menu. They are a little unfair - since on the master brach, my machine ran out of graphics memory after 6 consecutive opens on the master branch.Master
This Branch
Master
This Branch
Reviewers
@ColinDayOrg
@alfarok
FYIs