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 crashes by undoing/redoing nodes deletion #11021

Merged
merged 7 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/DynamoCore/Scheduler/UpdateGraphAsyncTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ protected override void HandleTaskCompletionCore()
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
private bool Contains(UpdateGraphAsyncTask other)
private bool CanReplace(UpdateGraphAsyncTask other)
{
// Be more conservative here. Not only check ModifiedNodes, but
// also check *all* nodes in graph sync data. For example, consider
Expand All @@ -175,9 +175,18 @@ private bool Contains(UpdateGraphAsyncTask other)
else if (other.graphSyncData == null)
return true;

return other.graphSyncData.AddedNodeIDs.All(graphSyncData.AddedNodeIDs.Contains) &&
// Merging additions and removals can make the changeSetComputer internal state get corrupted.
// Imagine a sequence of queued tasks with changes like these: +A | -A | +AB
// Imagine a new task with change -A, ending up with the following sequence: -A | +A | -A | +AB
// While the merge routine would simplify this sequence to: -A | +A | +AB
// That creates an inconsistent sequence of changes where subtree A is added when it already exists!
// Modifications are not susceptible to this problem. Imagine ~A incoming with sequence: +A | -A | ~A | +AB
// The resulting sequence is: ~A | +A | -A | +AB, which is valid.
// Because modifications do not create or remove entries in the changeSetComputer internal state,
// if we remove one from anywhere in the sequence we remain consistent.
return other.graphSyncData.AddedNodeIDs.Count() == 0 &&
other.graphSyncData.ModifiedNodeIDs.All(graphSyncData.ModifiedNodeIDs.Contains) &&
other.graphSyncData.DeletedNodeIDs.All(graphSyncData.DeletedNodeIDs.Contains);
other.graphSyncData.DeletedNodeIDs.Count() == 0;
}

private bool IsScheduledAfter(UpdateGraphAsyncTask other)
Expand All @@ -188,12 +197,13 @@ private bool IsScheduledAfter(UpdateGraphAsyncTask other)
protected override TaskMergeInstruction CanMergeWithCore(AsyncTask otherTask)
{
var theOtherTask = otherTask as UpdateGraphAsyncTask;
Copy link
Member

Choose a reason for hiding this comment

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

I think this change may have significant performance impacts - we should investigate the original reason for the implementation - my memory is that when moving a slider it kicked off hundreds of unnecessary runs and made dynamo unresponsive - if this is related then theres an argument that the crash is actually acceptable...?

Copy link
Collaborator Author

@mmisol mmisol Aug 18, 2020

Choose a reason for hiding this comment

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

Strange that was fixed by doing this instead of throttling the requests created by the slider 🤔

Still, maybe we could restore a more elaborate merge strategy that accounted for this case in particular. What kind of graphSyncData would we expect? Do we have a test case?

Copy link
Member

Choose a reason for hiding this comment

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

heres part of the discussion I recall:
#4276

Copy link
Member

Choose a reason for hiding this comment

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

and a related github issue:
#4229

Copy link
Collaborator Author

@mmisol mmisol Aug 18, 2020

Choose a reason for hiding this comment

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

Delay seems to work just fine according to my manual testing. Is there any special validation you would like to make beyond what's shown in that issue?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm concerned if there are other scenarios (other than the slider) where we are not dropping redundant tasks that are quickly accumulating as a result of this change thus hitting performance.

Copy link
Member

Choose a reason for hiding this comment

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

We don't control the UI that other package authors may build - delay for the slider is great, but it does not cover the case generally, though I guess we could document the idea somewhere to throttle their controls?

For example I am thinking of controls like:
image

and

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Granted. For those cases having a strategy to merge/discard would be useful. I'll try to come up with something.


if (theOtherTask == null)
return base.CanMergeWithCore(otherTask);

if (theOtherTask.IsScheduledAfter(this) && theOtherTask.Contains(this))
if (theOtherTask.IsScheduledAfter(this) && theOtherTask.CanReplace(this))
return TaskMergeInstruction.KeepOther;
else if (this.IsScheduledAfter(theOtherTask) && this.Contains(theOtherTask))
else if (this.IsScheduledAfter(theOtherTask) && this.CanReplace(theOtherTask))
return TaskMergeInstruction.KeepThis;
else
return TaskMergeInstruction.KeepBoth;
Expand Down
28 changes: 23 additions & 5 deletions src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -91,10 +92,27 @@ public override void OnNodeModified(NodeModel obj)

private void hwm_SetNodeDeltaState(object sender, DeltaComputeStateEventArgs e)
{
var nodeGuids = e.NodeGuidList;
// Using Nodes here is not thread safe, as nodes can be added/removed by the UI thread midway.
// Dispatching this to the UI thread helps avoid concurrency issues and seems more correct.
if (!DynamoViewModel.Model.ShutdownRequested)
{
DynamoViewModel.UIDispatcher.Invoke(() => UpdateNodesDeltaState(e.NodeGuidList, e.GraphExecuted));
}
else
{
// However, when Dynamo is shutting down, a funny deadlock situation occurs.
// The main thread is waiting for the scheduler to finish executing.
// So in order to avoid it, we do the update on the scheduler thread.
// Concurrency should not be an issue now that the UI is blocked waiting for shutdown.
UpdateNodesDeltaState(e.NodeGuidList, e.GraphExecuted);
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
}
}

private void UpdateNodesDeltaState(List<Guid> nodeGuids, bool graphExecuted)
{
// if runsettings is manual, and if the graph is not executed, then turing on showrunpreview
//should turn on showexectionpreview on every node.
if (nodeGuids.Count == 0 && !e.GraphExecuted)
if (nodeGuids.Count == 0 && !graphExecuted)
{
foreach (var nodeModel in Nodes)
{
Expand All @@ -104,7 +122,7 @@ private void hwm_SetNodeDeltaState(object sender, DeltaComputeStateEventArgs e)

//if the graph is executed then set the node preview to false , provided
// there is no error on that node.
if (nodeGuids.Count == 0 && e.GraphExecuted)
if (nodeGuids.Count == 0 && graphExecuted)
{
foreach (var nodeViewModel in Nodes)
{
Expand All @@ -113,7 +131,7 @@ private void hwm_SetNodeDeltaState(object sender, DeltaComputeStateEventArgs e)
nodeViewModel.ShowExecutionPreview = false;
nodeViewModel.IsNodeAddedRecently = false;
}
}
}
}

foreach (Guid t in nodeGuids)
Expand All @@ -132,7 +150,7 @@ private void hwm_SetNodeDeltaState(object sender, DeltaComputeStateEventArgs e)
{
if (nodes.ShowExecutionPreview)
nodes.ShowExecutionPreview = nodes.DynamoViewModel.ShowRunPreview;
}
}
}

void hwm_EvaluationCompleted(object sender, EvaluationCompletedEventArgs e)
Expand Down
2 changes: 1 addition & 1 deletion src/Libraries/CoreNodeModelsWpf/Controls/DynamoSlider.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
Maximum="{Binding Max}"
Minimum="{Binding Min}"
TickFrequency="{Binding Step}"
Value="{Binding Value}"
Value="{Binding Value, Delay=500}"
TickPlacement="None"
IsSnapToTickEnabled="true"
Grid.Column="2"
Expand Down