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

Added check for circular dependencies #3474

Merged
merged 5 commits into from
Apr 24, 2018

Conversation

etbyrd
Copy link
Contributor

@etbyrd etbyrd commented Apr 18, 2018

Customer scenario

In certain circumstances, there can be a circular dependency that creates a stack overflow when recursively checking for unresolved child dependencies, causing a complete VS crash.

Bugs this fixes:

https://devdiv.visualstudio.com/DevDiv/_workitems/edit/487858
#3374

Workarounds, if any

None

Risk

Low, there are some threading concerns that I am looking into to make sure that the Hashset is not shared - but I do not think that will be an issue because I am editing a private method and the class is instantiated when creating a snapshot.

Performance impact

Low. Just adding a HashSet.

Is this a regression from a previous update?

No

Root cause analysis:

There was no check that a dependency was not already resolved.

How was the bug found?

Customer feedback.

@etbyrd etbyrd changed the title added check for circular dependencies [WIP] added check for circular dependencies Apr 18, 2018
@etbyrd etbyrd requested review from davkean and a team April 18, 2018 03:49
@codecov-io
Copy link

codecov-io commented Apr 18, 2018

Codecov Report

Merging #3474 into dev15.7.x will increase coverage by 0.13%.
The diff coverage is 93.87%.

@@              Coverage Diff              @@
##           dev15.7.x    #3474      +/-   ##
=============================================
+ Coverage      68.29%   68.43%   +0.13%     
=============================================
  Files            731      731              
  Lines          36675    36719      +44     
  Branches        2119     2120       +1     
=============================================
+ Hits           25049    25128      +79     
+ Misses         11215    11172      -43     
- Partials         411      419       +8
Flag Coverage Δ
#debug 68.43% <93.87%> (+0.13%) ⬆️
Impacted Files Coverage Δ
...cies/Snapshot/TargetedDependenciesSnapshotTests.cs 97.82% <100%> (+0.12%) ⬆️
...endencies/Snapshot/TargetedDependenciesSnapshot.cs 76.92% <84.21%> (+19.7%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 93f5e53...52fa309. Read the comment docs.

@jmarolf
Copy link
Contributor

jmarolf commented Apr 18, 2018

@etbyrd is there a way to write a test for this case?

@@ -55,6 +55,8 @@ internal class TargetedDependenciesSnapshot : ITargetedDependenciesSnapshot
private Dictionary<string, bool> _unresolvedDescendantsMap
= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

private HashSet<string> _seenDependencies = new HashSet<string>();
Copy link
Contributor

Choose a reason for hiding this comment

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

private HashSet _seenDependencies = new HashSet(); [](start = 7, length = 67)

Why keep it global? It seems like implementation detail of FindUnresolvedDependenciesRecursive and don't have to stay in memory? We already keeping too much stuff in memory ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I will fix that

@abpiskunov
Copy link
Contributor

    public bool CheckForUnresolvedDependencies(IDependency dependency)

I know that tests for this method are missing here https://github.com/dotnet/project-system/blob/master/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Tree/Dependencies/Snapshot/TargetedDependenciesSnapshotTests.cs but i would expect them to be added with next changes and now looks like a good moment :)


Refers to: src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Tree/Dependencies/Snapshot/TargetedDependenciesSnapshot.cs:78 in 6a424b2. [](commit_id = 6a424b2, deletion_comment = False)

@etbyrd
Copy link
Contributor Author

etbyrd commented Apr 18, 2018

@jmarolf @abpiskunov I will be adding tests for this - I am still worried about some edge cases

@@ -55,6 +55,8 @@ internal class TargetedDependenciesSnapshot : ITargetedDependenciesSnapshot
private Dictionary<string, bool> _unresolvedDescendantsMap
= new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);



Copy link
Member

Choose a reason for hiding this comment

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

Nit: remove this added blank space.

@etbyrd etbyrd force-pushed the circular-dependency-check branch 2 times, most recently from b24daa6 to 6a424b2 Compare April 19, 2018 20:18
Copy link
Contributor

@abpiskunov abpiskunov left a comment

Choose a reason for hiding this comment

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

:shipit:

@etbyrd etbyrd changed the title [WIP] added check for circular dependencies Added check for circular dependencies Apr 20, 2018
@@ -573,6 +573,47 @@ public void TFromChanges_RemovedAndAddedChanges()
Assert.True(snapshot.DependenciesWorld.ContainsKey(@"tfm1\xxx\addeddependency3"));
}

/// <summary>
/// Added because circular dependencies can cause stack overflows
/// https://github.com/dotnet/project-system/pull/3474
Copy link
Contributor

Choose a reason for hiding this comment

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

Link to #3374 instead please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/// <summary>
/// Internal for testing.
/// </summary>
internal TargetedDependenciesSnapshot(IDictionary<string, IDependency> dependenciesWorld, IEnumerable<IDependency> topLevelDependencies)
Copy link
Member

Choose a reason for hiding this comment

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

I would rather make this constructor private, with in internal static factory method with a clear name like CreateForUnitTestingOnly or something.

//Checking here will prevent a stack overflow due to rechecking the same dependencies
if (_dependenciesChildrenMap.ContainsKey(child.Id))
{
unresolved = false;
Copy link
Member

Choose a reason for hiding this comment

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

I had to think about why this is sufficient to break cycles. My conclusion is that we don't have to worry about the unresolved case, because unresolved dependencies wouldn't have any children, is that right?

break;
}

//If the dependency is already in the child map, it is resolved
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We put a space after // in comments.

topLevelDependencies: new List<IDependency>() { dependencyModelTop1 });

// verify it doesn't stack overflow
previousSnapshot.CheckForUnresolvedDependencies(dependencyModelTop1);
Copy link
Member

Choose a reason for hiding this comment

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

Have you verified that this test fails without your fix?

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, it fails without the fix.

topLevelDependencies: new List<IDependency>() { dependencyModelTop1 });

// verify it doesn't stack overflow
previousSnapshot.CheckForUnresolvedDependencies(dependencyModelTop1);
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer if we didn't have the failure case here be: crash the test harness. That is going to be hard to diagnose if we ever regress this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I am working a way around this but I wonder if it is a requirement for this fix to be added.

@Pilchie Pilchie added this to the 15.7 milestone Apr 23, 2018
@etbyrd etbyrd changed the base branch from master to dev15.7.x April 24, 2018 17:41
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.

5 participants