Skip to content

Fix no-op restores for central package management#4523

Merged
jeffkl merged 3 commits intoNuGet:devfrom
marcin-krystianc:dev-maarcink-20220328-cpvm-noops
Apr 5, 2022
Merged

Fix no-op restores for central package management#4523
jeffkl merged 3 commits intoNuGet:devfrom
marcin-krystianc:dev-maarcink-20220328-cpvm-noops

Conversation

@marcin-krystianc
Copy link
Copy Markdown
Contributor

@marcin-krystianc marcin-krystianc commented Mar 28, 2022

Bug

Fixes: NuGet/Home#11696

Regression?
Last working version: dotnet 5.0.100. It has regressed because the runtime option: UseRandomizedStringHashAlgorithm wasn't enabled before but now it is.

Description

Hash calculation for no-op restore was relying on String.GetHashCode() method.
Unfortunately, there is a security feature (https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/userandomizedstringhashalgorithm-element#remarks) that makes string hashes calculated on a per-application domain basis. Therefore same string produces a different hash each time application is being run.
It is being fixed by replacing String.GetHashCode() with SHA512 calculation.

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

…tests

Hash calculation for noop restore was was on String.GetHashCode() method.
Unfortunately, there is a security feature (https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/userandomizedstringhashalgorithm-element#remarks) that makes string hashes calculated on a per application domain basis. Therefore same string produces different hash each time application is being run.
It is being fixed by replacing String.GetHashCode() with SHA512 calculation.
@marcin-krystianc marcin-krystianc requested a review from a team as a code owner March 28, 2022 20:22
@ghost ghost added the Community PRs created by someone not in the NuGet team label Mar 28, 2022
@jeffkl jeffkl self-assigned this Mar 28, 2022
{
var hashCode = new HashCodeCombiner();
foreach (var item in items)
using (var hashFunc = new Sha512HashFunction())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the idea behind that might've been a perf optimization.

I can only imagine the perf gains are now gone right? We can just add it to the original hashing function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Basically this whole compressed concept, might be something that needs to go away.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can only imagine the perf gains are now gone right? We can just add it to the original hashing function?
Basically this whole compressed concept, might be something that needs to go away.

Yeah, I didn't think that it matters but apparently it has a non-negligible effect. Surprisingly, it is not the hash function itself that is slow. In profiler I can see that the fact that centralised versions are being sorted and also the conversion of VersionRange to string makes the "non-compressed" path to be actually slow:

foreach (var dependency in centralPackageVersions.OrderBy(dep => dep.Name))
{
    writer.WriteNameValue(name: dependency.Name, value: dependency.VersionRange.ToNormalizedString());
}

I need to try more things to come up with final conclusion on how to handle this issue without sacrificing performance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we need to keep the "compressed" flag for now. Without it (7e53a50) the overall performance of (when measuring time of NuGet.ProjectModel.DependencyGraphSpec.GetHash() is almost 100% worse.
My latest commit (48c37d8) makes it only 10% worse. I think that it is acceptable cost for fixing no-op scenario.

We could try making it much faster by avoiding calculating a hash value multiple times for the same project. For large solutions that would mean massive savings. But it is a nontrivial change to do so I would like to keep it out of scope of this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's keep it then.

We could try making it much faster by avoiding calculating a hash value multiple times for the same project

The trick here is that you can't guarantee the package spec hasn't been changed.
Out of scope for this PR though. Thanks for validating that.

@jeffkl
Copy link
Copy Markdown
Contributor

jeffkl commented Mar 30, 2022

@nkolev92 do the "central" versions really matter? Whatever was resolved is what actually affects the graph and outcome. If you change a central version but it doesn't change the resolved versions of actually referenced packages, then a previous restore is still accurate right? I've seen these in the assets files and wondered why they are there, maybe we can just get rid of them?

@nkolev92
Copy link
Copy Markdown
Member

outcome. If you change a central version but it doesn't change the resolved versions of actually referenced packages, then a previous restore is still accurate right? I've seen these in the assets files and wondered why they are there, maybe we can just get rid of them?

Not if you change a central transitive dependency.

@jeffkl
Copy link
Copy Markdown
Contributor

jeffkl commented Mar 30, 2022

Not if you change a central transitive dependency.

Ah so this is the dgSpec which doesn't have resolved transitive dependencies?

@nkolev92
Copy link
Copy Markdown
Member

Yep

Copy link
Copy Markdown
Contributor

@jeffkl jeffkl left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution @marcin-krystianc!

@jeffkl jeffkl merged commit 081f5d0 into NuGet:dev Apr 5, 2022
@marcin-krystianc marcin-krystianc deleted the dev-maarcink-20220328-cpvm-noops branch April 6, 2022 07:38
@aelij
Copy link
Copy Markdown

aelij commented Jun 26, 2022

Will this fix be included in the next .NET 6 SDK release?

@nkolev92
Copy link
Copy Markdown
Member

It'll be in 6.0.400 SDK, 6.3 of NuGet, 17.3 of VS.

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

Development

Successfully merging this pull request may close these issues.

[Bug]: Central package management breaks no-op restores

4 participants