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

Ready for Review: Weigher Impl #156

Closed
wants to merge 9 commits into from
Closed

Ready for Review: Weigher Impl #156

wants to merge 9 commits into from

Conversation

digitalbuddha
Copy link
Contributor

Resolve #153

I was able to implement as extension function. Doing as a subclass of Builder did not work out due to build function taking a parameterized type. I could not figure out how to make that one the same as the class parameters on WeighingCacheBuilder

I can't get tests to run locally, will run and add new ones tomorrow.

My biggest question is whether expireEntries needs to reduce the total weight. Initially I had it reducing total weight but previous impl (copied from guava) did not do it.

https://github.com/nytimes/Store/blob/e4c21b23c80ba1c6a2f5c451066ed7adff3592f0/cache/src/main/java/com/nytimes/android/external/cache3/LocalCache.java#L2657

Bug or feature?

TODO Tests tomorrow

@@ -54,6 +54,14 @@ interface Cache<in Key : Any, Value : Any> {
*/
interface Builder {

@ExperimentalTime
var expireAfterWriteDuration: Duration
Copy link
Contributor Author

Choose a reason for hiding this comment

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

had to pull these into interface for extension build function. My question about these (for follow up) - do we need the setter functions or can we have a builder with only vars and set()

Copy link
Contributor

Choose a reason for hiding this comment

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

Setter functions return the instance whereas simple vars do not. So that allows chain of the method calls.

Comment on lines 38 to 39
val weigher: Weigher<Key, Value> = OneWeigher(),
val maxWeight: Long = UNSET_LONG
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the default parameters? this class is internal anyway so we can just choose the defaults in builder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the default builder is weigher agnostic it felt cleaner here.

fun weigh(key: K, value: V): Int
}

class OneWeigher<K : Any, V : Any> : Weigher<K, V> {
Copy link
Contributor

Choose a reason for hiding this comment

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

internal?

tasomaniac
tasomaniac previously approved these changes Apr 26, 2020
Copy link
Contributor

@tasomaniac tasomaniac left a comment

Choose a reason for hiding this comment

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

Looks good to me. If Store3 had tests around this, it would be good to bring those.

@digitalbuddha
Copy link
Contributor Author

digitalbuddha commented Apr 26, 2020 via email

@tasomaniac
Copy link
Contributor

We have some tests in Apollo testing our weigher implementation and it's Integration with the cache. Does Store have snapshots? I can quickly check and run those.

@tasomaniac
Copy link
Contributor

Any plans to finalize this? We need this in order to migrate from old version. Thanks.

@digitalbuddha
Copy link
Contributor Author

Yes this week. Sorry got pulled off and time flies

@tasomaniac
Copy link
Contributor

Thanks ❤️

@digitalbuddha
Copy link
Contributor Author

Ran into a race condition while trying to land. Will continue tonight (almost there)

@digitalbuddha
Copy link
Contributor Author

@tasomaniac should be good to go, mind reviewing?

@codecov
Copy link

codecov bot commented Jul 18, 2020

Codecov Report

Merging #156 into master will decrease coverage by 0.38%.
The diff coverage is 94.87%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #156      +/-   ##
============================================
- Coverage     86.01%   85.62%   -0.39%     
+ Complexity      230      229       -1     
============================================
  Files            54       56       +2     
  Lines           951      981      +30     
  Branches        149      156       +7     
============================================
+ Hits            818      840      +22     
- Misses           76       77       +1     
- Partials         57       64       +7     
Impacted Files Coverage Δ Complexity Δ
...dropbox/android/external/cache4/WeighingBuilder.kt 90.00% <90.00%> (ø) 0.00 <0.00> (?)
...n/com/dropbox/android/external/cache4/RealCache.kt 82.25% <95.45%> (-3.73%) 40.00 <6.00> (-1.00)
...otlin/com/dropbox/android/external/cache4/Cache.kt 100.00% <100.00%> (ø) 0.00 <0.00> (ø)
...lin/com/dropbox/android/external/cache4/Weigher.kt 100.00% <100.00%> (ø) 0.00 <0.00> (?)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1a954e4...24debc5. Read the comment docs.

@@ -78,6 +81,8 @@ internal class RealCache<Key : Any, Value : Any>(
*/
private val loadersSynchronizer = KeyedSynchronizer<Key>()

private var totalWeight = 0L
Copy link
Contributor

Choose a reason for hiding this comment

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

was this reverted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops good catch. I'll add tests for the new builder as well. BTW your tests were great and caught a logic error of mine

Copy link
Contributor

@ychescale9 ychescale9 left a comment

Choose a reason for hiding this comment

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

Some tests would be great 😃

@digitalbuddha digitalbuddha changed the title WIP Weigher Impl Ready for Review: Weigher Impl Aug 6, 2020
@digitalbuddha
Copy link
Contributor Author

@ychescale9 ready to review, I added tests. The most interesting one was test cache and evict nothing when caching something heavier than max weight which lead to a behavior change in our cache code. Guava cache's will not cache entries that are larger than max weight. Made a minor adjustment to short circuit the put logic. Could you kindly double check that this looks reasonable?

@@ -139,12 +148,22 @@ internal class RealCache<Key : Any, Value : Any>(
val existingEntry = cacheEntries[key]
if (existingEntry != null) {
// cache entry found
recordWrite(existingEntry, nowNanos)
// remove the weight of the current entry
totalWeight -= existingEntry.weight
Copy link
Contributor

Choose a reason for hiding this comment

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

The cache is multi-threaded and this is not thread safe.

This is actually not easy to fix without adding substantial synchronization to the cache (or adding other optimizations beyond the scope of this cache). The reason is that you need to make sure the state of cachedEntries and totalWeight is updated atomically.

Comment on lines 157 to +158
existingEntry.value = value
existingEntry.weight = weight
Copy link
Contributor

Choose a reason for hiding this comment

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

value and weight need to be updated atomically (e.g put them in pair or data class)

@digitalbuddha
Copy link
Contributor Author

#200 is the fix for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Weigher API is not available in Store 4
4 participants