Implement minimal implementation of HybridCache#55147
Conversation
Co-authored-by: Andrew Casey <amcasey@users.noreply.github.com>
Co-authored-by: Brennan <brecon@microsoft.com>
Co-authored-by: Brennan <brecon@microsoft.com>
Co-authored-by: Brennan <brecon@microsoft.com>
…into marc/hybrid-api
…teT.cs Co-authored-by: Safia Abdalla <safia@microsoft.com>
…netcore into marc/hybrid-api-basic
amcasey
left a comment
There was a problem hiding this comment.
This is where I ran out of time for today. Mostly simple questions.
| } | ||
| return value; | ||
|
|
||
| static void Throw() => throw new ObjectDisposedException("The cache item has been recycled before the value was obtained"); |
There was a problem hiding this comment.
The reason the JIT doesn't inline methods with throws is that it wants to keep them on the stack in the event of an exception?
|
|
||
| partial class DefaultHybridCache | ||
| { | ||
| internal readonly struct StampedeKey : IEquatable<StampedeKey> |
|
|
||
| // otherwise, either nothing existed - or the thing that already exists can't be joined; | ||
| // in that case, go ahead and use the state that we invented a moment ago (outside of the lock) | ||
| _currentOperations[stampedeKey] = stampedeState; |
There was a problem hiding this comment.
I think there is technically still a race here. Up to you to decide if it's worth fixing though.
There was a problem hiding this comment.
The only competition scenario involves the remove loop, but that would mean we also couldn't join with the thing being removed (or we would have exited 5 lines above), which means we're still doing the right thing by running our own, even if we end up being the only caller. I concede there may be some extreme edge case where we end up unnecessarily having a worker, but: our aim here is to reduce that to almost zero; at that point, we're good, IMO
| { | ||
| _key = key; | ||
| _flags = flags; | ||
| _hashCode = key.GetHashCode() ^ (int)flags; |
There was a problem hiding this comment.
I pointed to the apisof page because it tells you what frameworks the API exists on and what package you need to use on what framework.
TLDR; there is a package with the API for netfx.
| { | ||
| var reader = new Utf8JsonReader(source); | ||
| #pragma warning disable IDE0079 // unnecessary suppression: TFM-dependent | ||
| #pragma warning disable IL2026, IL3050 // AOT bits |
There was a problem hiding this comment.
Is this something that will be fixed in future previews?
| // give it a moment for the eviction callback to kick in | ||
| for (var i = 0; i < 10 && cacheItem.NeedsEvictionCallback; i++) | ||
| { | ||
| await Task.Delay(250); |
There was a problem hiding this comment.
I strongly dislike test code like this (even though I occasionally do it too), any way to make it deterministic instead?
There was a problem hiding this comment.
We're at the mercy of MemoryCache's timing on calling the eviction callback, which seems to be non-deterministic - I'm guessing they'r using QUWI. We could make it deterministic by adding an event or something that lets us know when that happens, but that is bending the design a bit far for testing. We could perhaps add a DEBUG-only event that we hook in DEBUG?
amcasey
left a comment
There was a problem hiding this comment.
I don't understand all of it, but I also don't see anything that makes me think we shouldn't merge it.
|
Ah, I missed that this was auto-merge. Well, I stand by my claim that it was mergeable, but I think a follow-up changing addressing the remaining feedback is in order. |
adding comment |
I can't speak for the "why", but: it does reduce the inlineable method size to the point where the main thing (the non-exceptional case) can often be inlined; I believe modern JIT can also see methods that always / only throw, and never try inlining, which again: can leave the non-exceptional cases more inlineable; we don't really care about the stack frames - it is more about body size, IIRC |
@BrennanConroy this really doesn't warrant an extra package; heck, we could probably remove the flags part of the hash and just use the key's hashcode, and we'd be absolutely fine |
|
@BrennanConroy race condition concerns addressed here: eeaf857 |
Implement minimal implementation of
HybridCacheThis is the first "make something that works" iteration of
HybridCache, that builds on theabstractAPI proposed in #55084 (epic: #53255 also: #54647); it will be re-targeted atmainas soon as #55084 is mergedBroad design overview in src/Caching/Hybrid/src/Internal/readme.md
Implemented this wave:
byte[]) and "buffer" (ReadOnlySequence<byte>,IBufferWriter<byte>) L2 backendsIDistributedCachevsIBufferDistributedCacheIBufferDistributedCache(and we found a SqlClient glitch: Inconsistent handling of empty BLOB slices as parameters SqlClient#2465)Explicitly not implemented yet:
Here's the basic headline numbers comparing
HybridCacheto a classIDistributedCache"get, test, deserialize | fetch+serialize+set" loop using a local Redis backend and a simple POCO type (the fetch is artificial, but the test is fundamentally a 100% hit scenario):(yes, callers could add L1 support, but many don't because it is awkward; stampede support is very tricky to get right, so is almost never implemented; the point of
HybridCacheis to make it easy to get all the right behaviors)