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 IsSubSetOfWithNullCheck (#11222) #4475

Merged
merged 7 commits into from Mar 8, 2022

Conversation

mjolka
Copy link
Contributor

@mjolka mjolka commented Feb 25, 2022

Bug

Fixes: NuGet/Home#11222

Regression? Last working version:

Description

IsSubSetOfWithNullCheck is supposed to return true when other is
a subset of parent.

This fixes an issue where restore can take a long time to run on
solutions with many references and packages.

A test solution was created with 42 projects, with project n referencing
projects 1..(n-1). Each project also had a couple of package references
and a NoWarn element.

Before this change, restore ran for 5 minutes, used ~14GB of memory,
and failed with an OutOfMemoryException ("Array dimensions exceeded
supported range.")

After this change, restore completed in under 5 seconds.

PR Checklist

  • PR has a meaningful title

  • PR has a linked issue.

  • Described changes

  • Tests

    • Automated tests added
    • OR
    • Test exception
    • OR
    • N/A
  • Documentation

    • Documentation PR or issue filled
    • OR
    • N/A

IsSubSetOfWithNullCheck is supposed to return true when `other` is
a subset of `parent`.

This fixes an issue where restore can take a long time to run on
solutions with many references and packages.

A test solution was created with 42 projects, with project n referencing
projects 1..(n-1). Each project also had a couple of package references
and a NoWarn element.

Before this change, restore ran for 5 minutes, used ~14GB of memory,
and failed with an OutOfMemoryException ("Array dimensions exceeded
supported range.")

After this change, restore completed in under 5 seconds.
@mjolka mjolka requested a review from a team as a code owner February 25, 2022 06:53
@ghost ghost added the Community PRs created by someone not in the NuGet team label Feb 25, 2022
@erdembayar
Copy link
Contributor

erdembayar commented Feb 25, 2022

@jeffkl
Could please take a look at this PR? I believe you have context on this.

@nkolev92
Copy link
Member

Can we please add tests for this?

`NodeWarningProperties.IsSubSetOf(other)` should return `true` when
`other.ProjectWide` is a subset of `ProjectWide` and each value of
`other.PackageSpecific` is a subset of the corresponding value in
`PackageSpecific`.

Note that the order is reversed compared to
`HashSet<T>.IsSubsetOf(other)`.

This commit fixes a couple of cases:

- when `PackageSpecific[a]` is a proper subset of `other.PackageSpecific[a]` (should return `false`)
- when `PackageSpecific[a]` is a proper superset of `other.PackageSpecific[a]` (should return `true`)
- when `PackageSpecific` is an empty dictionary and `other.PackageSpecific[a]` is empty (should return `true`)
- when `PackageSpecific` is `null` and `other.PackageSpecific[a]` is empty (should return `true`)
@mjolka
Copy link
Contributor Author

mjolka commented Feb 28, 2022

I've added unit tests and in the process found and fixed an issue with NodeWarningProperties.IsSubSetOf.

I've also created a repo with a solution I described in the original comment, where a restore runs for several minutes and dies with a OOM exception. You can find it here: https://github.com/mjolka/LotsOfDependencies

If you have any suggestions on how to turn that into a test case, please let me know.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

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

LGTM.
Thanks for this change!

Just some tweaks to the tests before approving.

@erdembayar
Copy link
Contributor

I've added unit tests and in the process found and fixed an issue with NodeWarningProperties.IsSubSetOf.

I've also created a repo with a solution I described in the original comment, where a restore runs for several minutes and dies with a OOM exception. You can find it here: https://github.com/mjolka/LotsOfDependencies

If you have any suggestions on how to turn that into a test case, please let me know.

Thank you. Is performance improvement still holding after your recent changes?
Could you include before and after output windows log?

@mjolka
Copy link
Contributor Author

mjolka commented Mar 2, 2022

Sorry, pushed those changes by accident.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

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

Awesome fix.

Thanks @mjolka

@nkolev92
Copy link
Member

nkolev92 commented Mar 4, 2022

@NuGet/nuget-client Last call for reviews.
I'll try to merge Monday afternoon if there's no additional feedback.

@mjolka
Copy link
Contributor Author

mjolka commented Mar 5, 2022

@nkolev92 Thanks for your feedback. I was going to make some further changes to the test cases today but if you're happy with the state of the PR I'll leave it alone.

Would you like me to squash those "wip" commits and force push?

@erdembayar The performance improvement still holds after the recent changes.

I've attached the output of running NuGet.exe restore .\src\LotsOfDependencies.sln -Force -ForceEvaluate before and after the changes, is that what you were asking for?

before.txt

after.txt

@nkolev92
Copy link
Member

nkolev92 commented Mar 7, 2022

@mjolka We squash and merge, no need to squash in your branch. I sometimes do it on the developing branch as it makes rebasing easier.

What kind of test changes were you thinking of?

@mjolka
Copy link
Contributor Author

mjolka commented Mar 8, 2022

@nkolev92 I was thinking of replacing the PackageSpecificNoWarn helper enum with a string based format for the test cases. Here's an example: mjolka@41f8928

@nkolev92
Copy link
Member

nkolev92 commented Mar 8, 2022

That's a good change. No changes to adding/removing test cases right?

@mjolka
Copy link
Contributor Author

mjolka commented Mar 8, 2022

@nkolev92 Right, no changes to adding/removing test cases. This would be the change: mjolka@d4bd3e3

@nkolev92
Copy link
Member

nkolev92 commented Mar 8, 2022

feel free to push if you'd like to.
I'll rerun the tests when you do.

@mjolka
Copy link
Contributor Author

mjolka commented Mar 8, 2022

Thanks @nkolev92 .

I just noticed what might be a bug in NodeWarningProperties.Intersect(HashSet<NuGetLogCode> first, HashSet<NuGetLogCode> second) and wanted to get your thoughts on it as it's closely related to this change.

In the method above Intersect, IsSubSetOfWithNullCheck, null is considered to be the empty set:

private static bool IsSubSetOfWithNullCheck(HashSet<NuGetLogCode> parent, HashSet<NuGetLogCode> other)
{
// Null is empty and always a subset.
if (other == null || other.Count == 0)
{
return true;
}
// A null or empty parent cannot be a superset.
if (parent == null || parent.Count == 0)
{
return false;
}
if (other.Count <= parent.Count)
{
return parent.IsSubsetOf(other);
}
return false;
}

Following that logic, Intersect(first, null) and Intersect(null, second) should both return null (or an empty HashSet). However, Intersect(first, null) returns first and Intersect(null, second) returns second.

private static HashSet<NuGetLogCode> Intersect(HashSet<NuGetLogCode> first, HashSet<NuGetLogCode> second)
{
if (ReferenceEquals(first, second))
{
return first;
}
if (first == null)
{
return second;
}
if (second == null)
{
return first;
}
var result = new HashSet<NuGetLogCode>(first);
result.IntersectWith(second);
return result;
}

@nkolev92
Copy link
Member

nkolev92 commented Mar 8, 2022

I think it's ok if null and empty are considered the same for practical purposes.
If you do want to make changes there, can we please do that in a different PR? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community PRs created by someone not in the NuGet team
Projects
None yet
4 participants