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

Cache LastAccessed during MemoryCache compaction #61187

Merged
merged 3 commits into from
Dec 2, 2021

Conversation

eerhardt
Copy link
Member

@eerhardt eerhardt commented Nov 3, 2021

During cache compaction, we are sorting entries based on their LastAccessed time. However, since the cache entries can still be used concurrently on other threads, the LastAccessed time may be updated in the middle of sorting the entries. This leads to exceptions in a background thread, crashing the process.

The fix is to cache the LastAccessed time outside of the entry when we are adding it to the list. This will ensure the time is stable during the compaction process.

Fix #61032

During cache compaction, we are sorting entries based on their LastAccessed time. However, since the cache entries can still be used concurrently on other threads, the LastAccessed time may be updated in the middle of sorting the entries. This leads to exceptions in a background thread, crashing the process.

The fix is to cache the LastAccessed time outside of the entry when we are adding it to the list. This will ensure the time is stable during the compaction process.

Fix dotnet#61032
@ghost
Copy link

ghost commented Nov 3, 2021

Tagging subscribers to this area: @eerhardt, @maryamariyan, @michaelgsharp
See info in area-owners.md if you want to be subscribed.

Issue Details

During cache compaction, we are sorting entries based on their LastAccessed time. However, since the cache entries can still be used concurrently on other threads, the LastAccessed time may be updated in the middle of sorting the entries. This leads to exceptions in a background thread, crashing the process.

The fix is to cache the LastAccessed time outside of the entry when we are adding it to the list. This will ensure the time is stable during the compaction process.

Fix #61032

Author: eerhardt
Assignees: -
Labels:

area-Extensions-Caching

Milestone: -

Comment on lines +17 to +19
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
</ItemGroup>
Copy link
Member

Choose a reason for hiding this comment

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

Backporting a fix that has new dependencies seems problematic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Should I create a new private struct for this data instead of using a ValueTuple?

Copy link
Member Author

Choose a reason for hiding this comment

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

@ericstj @davidfowl - any thoughts here? If we decide to backport this to 6.0.x, would this new dependency on the System.ValueTuple package be a problem? Nothing in MS.Ext.Caching.Memory brings in this package today on netfx.

I can easily switch to not use value tuples, but it seems like a pain going forward.

One option could be use value tuples in 7.0 going forward, and just use a custom struct in the 6.0 version.

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 just stick to a small internal struct for now. It's much less intrusive and doesn't require an ifdef. We should then decide when we rip the netstandard 2.0 cord. (.NET 7 or 8?)

Copy link
Member Author

@eerhardt eerhardt Nov 9, 2021

Choose a reason for hiding this comment

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

doesn't require an ifdef

The plan I'm proposing doesn't require an ifdef (beyond this PackageReference). The idea is:

For the 7.0.0+ versions of this package (in main) we use ValueTuple like I am here. Then it is available in the future if we want to use it in other places. If servicing 6.0 wasn't a consideration, this is the plan I would take without a 2nd thought.

For the 6.0.1 version of this package (in release/6.0) change the ValueTuple uses in this PR to a small struct and remove the dependency on System.ValueTuple. That way we don't add the new dependency in a service release.

We should then decide when we rip the netstandard 2.0 cord. (.NET 7 or 8?)

The plan above fits great with this. If/when we rip the netstandard2.0 cord, this PackageReference goes away and the C# code doesn't change at all, since ValueTuple is available inbox on NETCOREAPP.

Copy link
Member

Choose a reason for hiding this comment

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

The ValueTuple dependency can be a real PITA for users who are using a very old VS that generates invalid binding redirects for .NET Framework projects: dotnet/BenchmarkDotNet#1687 (comment)

I doubt that they use latest MemoryCache version and I think that we should just stop targeting .NET Standard 2.0. But we need to verify that with some data.

@danmoseley
Copy link
Member

One option could be use value tuples in 7.0 going forward, and just use a custom struct in the 6.0 version.

+1

@eerhardt eerhardt closed this Nov 8, 2021
@eerhardt eerhardt reopened this Nov 8, 2021
@eerhardt
Copy link
Member Author

eerhardt commented Nov 8, 2021

One option could be use value tuples in 7.0 going forward, and just use a custom struct in the 6.0 version.

I plan on taking this approach unless I hear objection. I plan on merging this for 7.0 once CI is green. When I backport to 6.0, I will make the appropriate changes to not add an additional dependency in the 6.0.1 package.

ayende added a commit to ayende/ravendb that referenced this pull request Nov 11, 2021
- Pulling MemoryCache fixes from  dotnet/runtime#57631 and dotnet/runtime#61187
ayende added a commit to ayende/ravendb that referenced this pull request Nov 19, 2021
- Pulling MemoryCache fixes from  dotnet/runtime#57631 and dotnet/runtime#61187
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

LGTM, thank you @eerhardt ! :shipit:

Comment on lines +17 to +19
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
</ItemGroup>
Copy link
Member

Choose a reason for hiding this comment

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

The ValueTuple dependency can be a real PITA for users who are using a very old VS that generates invalid binding redirects for .NET Framework projects: dotnet/BenchmarkDotNet#1687 (comment)

I doubt that they use latest MemoryCache version and I think that we should just stop targeting .NET Standard 2.0. But we need to verify that with some data.

@eerhardt
Copy link
Member Author

The ValueTuple dependency can be a real PITA for users who are using a very old VS that generates invalid binding redirects for .NET Framework projects

We use it elsewhere:

<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />

<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />

<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />

I'd guess the odds that users using very old VS on netfx using the latest version of this package, but not any of those is pretty low.

@eerhardt eerhardt merged commit b1fcbb6 into dotnet:main Dec 2, 2021
@eerhardt eerhardt deleted the FixMemoryCacheCompactionRace branch December 2, 2021 00:22
@adamsitnik adamsitnik added this to the 7.0.0 milestone Dec 2, 2021
eerhardt added a commit to eerhardt/runtime that referenced this pull request Dec 2, 2021
* Cache LastAccessed during MemoryCache compaction

During cache compaction, we are sorting entries based on their LastAccessed time. However, since the cache entries can still be used concurrently on other threads, the LastAccessed time may be updated in the middle of sorting the entries. This leads to exceptions in a background thread, crashing the process.

The fix is to cache the LastAccessed time outside of the entry when we are adding it to the list. This will ensure the time is stable during the compaction process.

Fix dotnet#61032
safern pushed a commit that referenced this pull request Dec 15, 2021
* Cache LastAccessed during MemoryCache compaction (#61187)

* Cache LastAccessed during MemoryCache compaction

During cache compaction, we are sorting entries based on their LastAccessed time. However, since the cache entries can still be used concurrently on other threads, the LastAccessed time may be updated in the middle of sorting the entries. This leads to exceptions in a background thread, crashing the process.

The fix is to cache the LastAccessed time outside of the entry when we are adding it to the list. This will ensure the time is stable during the compaction process.

Fix #61032

* Update fix for 6.0.x servicing.

1. Remove the dependency on ValueTuple and use a custom struct instead.
2. Add servicing package changes.
3. In the tests, move the DisableParallelization collection declaration in the test project, since it is only in "Common" in main.
@ghost ghost locked as resolved and limited conversation to collaborators Jan 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Race condition in MemoryCache will crash the process
5 participants