Skip to content

Conversation

@mjkkirschner
Copy link
Member

@mjkkirschner mjkkirschner commented Nov 30, 2017

Purpose

HomeWorkspaceViewModel was 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 Dispose methods to the viewModels which are called when the models are removed from their collections - for example, the NodeViewModel is disposed when its corresponding model is removed from the Nodes Collection on the WorkspaceModel.

These dispose methods cleanup event subscriptions that the viewModels had retaining them. These events point towards models, other viewModels, static instances etc...

  • I will provide a pre and post profile image.

Declarations

Check these if you believe they are true

  • The code base is in a better state after this PR
  • Is documented according to the standards
  • The level of testing this PR includes is appropriate
  • User facing strings, if any, are extracted into *.resx files
  • All tests pass using the self-service CI.
  • Snapshot of UI changes, if any.
  • Changes to the API follow Semantic Versioning, and are documented in the API Changes document.

pictures!

Please note that these runs show the difference between opening the graph Geometry_Surfaces.dyn 10 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. ⚠️ 😢 (machine is a VM with 512 mb of vram). This did not occur on this branch.

Master

graphpredispose

This Branch

graphafterdispose

Master

memorypredispose

This Branch

memoryafterdispose

Reviewers

@ColinDayOrg
@alfarok

FYIs

if (currentWorkspaceViewModel == viewModel)
if(currentWorkspaceViewModel != null)
{
currentWorkspaceViewModel.Dispose();
Copy link
Member Author

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;
Copy link
Collaborator

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)?

Copy link
Member Author

@mjkkirschner mjkkirschner Nov 30, 2017

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.

Copy link
Contributor

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?

Copy link
Collaborator

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?

@jnealb
Copy link
Collaborator

jnealb commented Nov 30, 2017

@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();
Copy link
Contributor

@alfarok alfarok Nov 30, 2017

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

will remove.

@mjkkirschner
Copy link
Member Author

@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.

@mjkkirschner
Copy link
Member Author

finding some more retention paths for HomeWorkspaceViewModel when workspace has watch3d nodes, investigating.


private void CloseHomeWorkspace(object parameter)
{
//TODO potentially dispose viewModel here.
Copy link
Contributor

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.
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Member Author

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(); };
Copy link
Contributor

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?
Copy link
Contributor

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

@ColinDayOrg
Copy link
Contributor

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
@mjkkirschner mjkkirschner added the DNM Do not merge. For PRs. label Dec 2, 2017
if (model3D != null)
{
model3D.Detach();
model3D.Dispose();
Copy link
Member Author

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);
Copy link
Member Author

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()
Copy link
Member Author

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
@mjkkirschner mjkkirschner added PTAL Please Take A Look 👀 and removed DNM Do not merge. For PRs. WIP labels Dec 5, 2017
@mjkkirschner
Copy link
Member Author

@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;
Copy link
Contributor

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).

Copy link
Member Author

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.

@ColinDayOrg
Copy link
Contributor

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.

@mjkkirschner
Copy link
Member Author

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.

@mjkkirschner
Copy link
Member Author

so after this last commit (shot in the dark) the self serve - CI passes. - I will run it again though and see what happens.

@mjkkirschner mjkkirschner merged commit dae4505 into DynamoDS:master Dec 7, 2017
@mjkkirschner mjkkirschner changed the title [WIP] viewModel memory leaks viewModel memory leaks Dec 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PTAL Please Take A Look 👀

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants