-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
HybridCache - minor post-PR fixes #55251
Conversation
Hi, I may easily be missing something but don't you have to check the local cache again after you find you are the stampede that needs to do the work? There's a race between the dictionaries where someone was adding the value and removed themselves from the |
@AnthonyLloyd can you outline the scenario you're thinking of, that isn't guarded by the interlocked join and the double-checked locking? The only scenario I can see there is when an in-flight operations is on the cusp of terminating, which means the try-join inside the double-checked lock fails. If that is the scenario you're thinking of, we could potentially take the lock for the final decrement+remove. That is rare but theoretically non-zero - I could try to measure impact of adding a lock here (probably negligible because we try gard to minimize the locks on the competing path). Or are you thinking of another scenario? |
@mgravell I think it's what you describe: |
OK. Probably won't be ready in time for preview 4, but I'll see what the impact of preventing that scenario is. |
Fun fact: in an incomplete commit earlier, I actually had a partitioned sync-lock grid to avoid any remaining contention on the lock - I ditched that when the only use was the double-check, which is itself rare. If we are going to lock on the remove path, it is probably worth resurrecting that; just a simple partition just using the low 3 bits of the key's hashcode. |
…the fact that an outgoing previous operation may have added it 2. remove any reference to IDisposable; that creates semantically invalid outcomes
@mgravell Ah I see you are now checking L1 again. Thanks |
@AnthonyLloyd the problem there, though, is that I'm not very happy with running "deserialize" inside the sync-lock; I can fix that, but it involves moving a few more pieces; sigh |
CI failure is in |
The new comments look great - thanks! The new code looks non-trivial and I'll have to look at it more carefully. @BrennanConroy will probably be interested too (I think you directed his attention here in comments on the original PR). |
I'll look into the CI in the morning, ta |
This is supplementary post-PR (it was auto-merged) tweaks requested in #55147, mostly for @amcasey - additional comments, etc
Additional notes on 7f8fbc6 - this removes any special treatment of
IDisposable
; this is because trying to consider it causes semantic problems; the caller can't know the lifetime, so if we did this, we would have a scenario where:because during the "... other stuff", the shared instance got evicted from cache and disposed; this is unpredictable and not a supportable scenario. The correct behaviours are:
using var obj = cache.GetOrCreateAsync(...)
, or(the latter being less "correct" than the former)
Both of these are what we already get from simply not considering
IDisposable
at all; 1. is what we get if the type is considered mutable, and 2. is what we get if it is considered immutable; so: it is already in the consumer's hands.In reality, it very much isn't worth worrying to much about
IDisposable
here; the intended use-case of cache is for transient state data, typically POCOs - not connected services etc (we're not the DI layer). IMO we should not overly distort any part of the design worrying about a pathological and hypothetical use-case that is probably best avoided with the words: "don't do that".additional notes on 223f1bd - this avoids having to deserialize inside the sync-lock (which is horrible), but to do that we need a single unified ref-count rather than the two separate ref-counts we had previously (one for the stampede state, one for the cache item), so that we can speculatively reserve our value without having to deserialize it; this actually simplifies the logic, so is desirable in its own right.