-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Synchronous Cache might block on put() #224
Comments
Yeah, mostly. We caught the stacktrace on 2.6.1 (essentially the put() call is blocked on CHM). |
Actually, it wasn't a refresh that blocks it but a get():
cache.put() is where it blocks, the assertEquals works though. |
And the Async version doesn't block.
|
Oh, this is as expected, isn't it? This is a The async version is much simpler because the mapping is established immediately and the value computed asynchronously. In that case you can replace an in-flight future. If you instead use the |
So the synchronous version actually has blocking puts against the same key then (compared to the async flavor). Always thought the asynchronous version exhibits the same behavior but enables asynchronous programming paradigms. We did ultimately just switch to the async cache but perhaps this is just a documentation issue then (or maybe it was, I dunno). |
Of interest, get() with a supplier has the same behavior for both synchronous and asynchronous versions (which I assume it would since in both cases the computation is in flight and so it ignores the supplier).
|
We can definitely update the JavaDoc if suggestions (or PR). I do think it makes sense for In the async case, you are establishing a mapping immediately and the future populates asynchronously. It can fail, or be null, and any consumers will receive that. You are explicitly asking for it to work outside of a One could argue that its okay to do similar for a synchronous case, like Guava did, but you'll run into linearization complaints like the issue above. The choice is gray for a cache, but wasn't for a regular map which is why |
Alright, this makes sense. I guess this is mainly going to be a synchronous loading cache issue. Background refreshes are fine in that case too as far as we can tell (meaning if a background refresh is in flight, it does get preempted by the put). Perhaps it's just the load() part that can potentially block puts that needs to have somewhat of a gotcha. |
Yes. But also consider the return value of asMap().put when a load is in progress. Should it block, replace, and return the loaded value? Or should it insert and the load be canceled as a replacement? Both are valid implementation decisions, I think. |
Yeah, this led me to think that perhaps you want this behavior to be configurable (even perhaps to cancel the future and interrupt even). Anyways, closing since the issue should really either be a) a documentation request/PR or b) configurable behavior for synchronous loading cache when dealing with puts (although even for asynchronous case you could say that whichever return first, the refresh vs the put could determine the replacement policy). |
(a) This would be great if you have a PR or suggestions for the FAQ page. P.S. Closing per "Anyways, closing since the issue..." |
A Have you considered using an |
Sorry, I missed your last paragraph. You can use the synchronous view to minimize code impact by interacting like a LoadingCache |
I ended up into thus thread when a put &refresh combination got blocked. Sorry for posting on a closed issue thread I was trying to replace guava with caffeine and implement totally async cache using getIfPresent and refresh only (no get() call as it loads the data inline. In my case if cache entry is not present, i call a refresh() and i continue without it but strictly dont want to block). Some observations i made: With the usecase above i ended up calling too many refresh() calls as cache entry initially was not loaded. This might be the case only during initial load as subsequent loads are taken care by refreshAfterWrite() To stop this behavior i tried to load an initial dummy value into the cache by calling put(k, dummyval) and then call a refresh(), i ended up into a put() block. Idea here was that for calls after first one, i dont end up getting a null as return for getIfPresent() and do not keep calling refresh() which i mentioned in #1 above. I assumed, refresh would ultimately replace the dummyVal. Pseudocode #1: Pseudocode #2: Is AsyncLoadingCache solution for the usecase (when will async loadcache get loaded? as i do not want to call a future.get() and blocked), i have not explored it yet as it needs some changes to make in my code which was using guava cache earlier. |
Sure. I just deleted my comment thinking there will be late reply and i thought il post it again once i do a little more work on this. But u noticed there was reply from you and i have put my comment back after 2 of your comments so that other readers do not lose the context. Thank you for blazingly fast reply too. |
Just a doubt on AsyncLoadingCache.get(), it returns me a future. Is it necessary for me to do a get on it(future) in order to load the cache? Because in my usecase, I am ok to not have a cache entry at that moment but make sure it loads some point of time later and i ultimately find it. I would not want to do a blocking call anywhere in my main thread where i lookup into cache. Also, when i deleted my comment i thought il do this below change and see if i can avoid an explicit refresh() call on the cache. For the first time(i have an external way to find first ever call to the cache lookup not just rely on null return for getIfPresent()), just set the dummy value to the cache and do not refresh it explicutly as refreshAfterWrite will anyway trigger async refresh) |
Your main thread doesn’t need to block, as the future will execute by default on a ForkJoinPool. You can query its state or chain handlers, so that should allow your caller to skip if still computing. |
I am trying to get a test to reproduce this but we have observed that a synchronous cache can block on put() calls if there's a background refresh going on (it's blocked within the ConcurrentHashMap).
I would have expected the code to either abandon the in-flight refresh (continue execution but ignore the results) or accept the put() and when the refresh returns, overwrite the value that was supplied by the put(). Instead, the put() call blocks.
This does not seem to happen to the Async flavor.
The text was updated successfully, but these errors were encountered: