Skip to content

Conversation

@reddyashish
Copy link
Contributor

@reddyashish reddyashish commented May 9, 2025

Purpose

Based on the changes here: #15856

After these changes, the workspace close time is reduced but there is still room for optimization.

Profiling results:

Before these changes, time to close

  1. Entry 05_Christmas Tree : 4 secs
  2. Autodesk-MaRS-office-example-v3 : 8 secs
  3. Car_Configurator_2026.0.2 : around 3 mins

After new changes:

  1. Entry 05_Christmas Tree : 2 secs
  2. Autodesk-MaRS-office-example-v3 : 4 secs
  3. Car_Configurator_2026.0.2 : 34 secs.

Investigating the WorkspaceViewModel, where Model_NodesCleared() and Model_NotesCleared() are triggered when the nodes are cleared in the workspace model. These 2 calls take 33% of the total close time so looking into it more. After investigation, found that when the objects under the ViewModel are not disposed correctly when a workspace is closed, it will affect the load time for the next workspace.

Declarations

Check these if you believe they are true

  • The codebase 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.
  • This PR modifies some build requirements and the readme is updated
  • This PR contains no files larger than 50 MB

Release Notes

DYN-8407 Optimizing workspace close operations by refactoring the dispose methods.

Reviewers

@DynamoDS/eidos

Comment on lines +1351 to +1364
private void DisposePort(PortModel portModel, bool nodeDisposing = false)
{
portModel.PropertyChanged -= OnPortPropertyChanged;
portModel.ConnectorCollectionChanged -= ConnectorsCollectionChanged;

// if this node is being disposed, we don't need to set node state and destroy the connectors,
// as the connectors will be deleted elsewhere
if (!nodeDisposing)
{
portModel.DestroyConnectors();
SetNodeStateBasedOnConnectionAndDefaults();
}
portModel.Dispose();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This would look cleaner if this logic is refactored in the PortModel.Dispose method somehow.

Comment on lines 1523 to 1532
foreach (NodeModel node in Nodes)
{
node.RaisesModificationEvents = false;
// Dispose here so that all nodes stop listening to disconnect events before
// the connectors are deleted. Otherwise remaining undisposed nodes will react
// to delete events when an input connector is deleted.
node.Dispose();
}

foreach (NodeModel el in Nodes)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't both these for loops be combined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to dispose the nodes first, otherwise when the connectors are deleted the undisposed nodes will react to events and will take more time. Refer to comment in line 1526.

Copy link
Contributor

Choose a reason for hiding this comment

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

Understood. This brings me to my next question - shouldn't the nodes be unsubscribed from those events before they are deleted? There could be a memory leak if they are not unsubscribed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, unsubscribing the nodes before disposing them in the workspaceviewmodel.

@reddyashish reddyashish changed the title WIP - Workspace close optimization DYN-8407 Workspace close optimization May 19, 2025
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-8407

@zeusongit
Copy link
Contributor

@reddyashish Can you share how much is the optimization gain/reduction in closing time after this change?

@reddyashish
Copy link
Contributor Author

@zeusongit Yes added the close times before and after these changes.

@reddyashish
Copy link
Contributor Author

Looking into the failing tests.

@zeusongit zeusongit requested a review from Copilot May 22, 2025 15:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses workspace closing optimizations by improving disposal logic across related view and model classes to reduce resource leakage and improve load times.

  • Updated disposal of nodes, annotations, and ports within WorkspaceViewModel, WorkspaceModel, and NodeModel.
  • Added unsubscribing from collection change events in PortModel.
  • Wrapped connector removals in try-catch to mitigate deletion exceptions.

Reviewed Changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs Removed node disposal loop in Model_NodesCleared, potentially risking undisposed node view models.
src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs Improved node disposal by deactivating events and ensuring nodes are disposed before clearing.
src/DynamoCore/Graph/Nodes/PortModel.cs Introduced event subscription/unsubscription for connector collection changes.
src/DynamoCore/Graph/Nodes/NodeModel.cs Added a dedicated DisposePort() method to better manage port disposal.
src/DynamoCore/Graph/ModelBase.cs Added a HasBeenDisposed flag to prevent multiple disposals.
src/DynamoCore/Graph/Connectors/ConnectorModel.cs Wrapped connector removals in try-catch and provided logging on exception.
Files not reviewed (1)
  • src/DynamoCore/PublicAPI.Unshipped.txt: Language not supported
Comments suppressed due to low confidence (1)

src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs:949

  • Removing the loop that disposes each node view model may lead to memory leaks if the nodes are not being disposed elsewhere. Consider ensuring that all node view models are properly disposed when the Nodes collection is cleared.
foreach (var nodeViewModel in Nodes)

@reddyashish
Copy link
Contributor Author

reddyashish commented May 28, 2025

Now that the whole build log is loaded, I noticed that it was missing another public API declaration. This should fix it.
@zeusongit @aparajit-pratap Please take a final look.

@reddyashish reddyashish merged commit db35a6d into DynamoDS:master May 28, 2025
27 checks passed
Comment on lines 1377 to 1383
p.Connectors.CollectionChanged += (coll, args) =>
{
// Call the collection changed handler, replacing
// the 'sender' with the port, which is required
// for the disconnect operations.
ConnectorsCollectionChanged(p, args);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

What about unsubscribing p.Connectors.CollectionChanged? Since it's attached to an anonymous function, you can't unsubscribe it unless you name the function (lambda).

Comment on lines +2954 to +2965
InPorts.CollectionChanged -= PortsCollectionChanged;
foreach(var port in InPorts)
{
DisposePort(port, nodeDisposing: true);
}

OutPorts.CollectionChanged -= PortsCollectionChanged;
foreach(var port in OutPorts)
{
DisposePort(port, nodeDisposing: true);
}
}
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 usually recommended to clean up your resources (unsubscribe from events, etc.) before calling base.Dispose().

Copy link
Contributor

@aparajit-pratap aparajit-pratap May 29, 2025

Choose a reason for hiding this comment

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

Is HasBeenDisposed being set to true in the base Dispose() method? If not, you need to set it to true at the end of this function.

if (HasBeenDisposed) return;

base.Dispose();
Connectors.CollectionChanged -= Connectors_CollectionChanged;
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 usually recommended to clean up your resources (unsubscribe from events, etc.) before calling base.Dispose().


public override void Dispose()
{
if (HasBeenDisposed) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is HasBeenDisposed being set to true in the base Dispose() method? If not, you need to set it to true at the end of this function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants