-
Notifications
You must be signed in to change notification settings - Fork 11k
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 load + compute methods are incompatible #2827
Comments
It sounds like this shouldn't be too difficult to deal with just by switching to a sentinel value, but if someone wants to send a pull request it'd probably get done more quickly. Otherwise I'll try to take a whack at this...soon-ish. |
Actually there are simpler fixes. A sentinel value would be invasive by requiring all read/write methods to check and translate, and computers to allocate a function to translate a null to the sentinel.
All would require the cache loading calls to handle a retry loop. These two (or perhaps a similar variant) would be easy to implement and isolates the changes. We could also run a snapshot through the test suites to verify. |
The asMap().compute implementation did not take into account that the present value may be loading. A load does not block other writes to that entry and takes into account that it may be clobbered, causing it to automatically discard itself. This is a known design choice that breaks linearizability assumptions (google#1881). The compute should check if a load is in progress and call the appropriate internal removal method. Because a zombie entry remained in the cache and still is marked as loading, the loader could discover entry and try to wait for it to materialize. When the computation is a removal, indicated by a null value, the loader would see this as the zombie's result. Since a cache loader may not return null it would throw an exception to indicate a user bug. A new ComputingValueReference resolves both issues by indicating that the load has completed. The compute's removeEntry will then actually remove this entry and the loader will not wait on the zombie. Instead if it observes the entry, it will neither receive a non-null value or wait for it to load, but rather try to load anew under the lock. This piggybacks on the reference collection support where an entry is present but its value was garbage collected, causing the load to proceed. By the time the lock is obtained the compute method's entry was removed and the load proceeds as normal (so no unnecessary notification is produced). fixes google#5342 fixes google#2827 resolves underlying cause of google#2108
A
Map.compute
may returnnull
to indicate the entry should be removed if present. ThevalueReference
is stored within the cache, allowing a subsequentLoadingCache.get
to wait on it. This results inLocalCache.waitForLoadingValue
to throw anInvalidCacheLoadException
with the messageCacheLoader returned null for key
. Since the cache loader was not invoked, this message is incorrect and means the two methods cannot be used together, concurrently.The user expectation would be that the load proceeds by retrying the load if
null
from a computation (but fails if from a cache loader). That may require using a sentinel value.You can checkout this commit and run
MultiThreadedTest
using./gradlew slowGuavaTest
.The text was updated successfully, but these errors were encountered: