Skip to content

Fix EvictableCacheBuilder#expireAfterWrite behavior#14476

Closed
atanasenko wants to merge 1 commit intotrinodb:masterfrom
atanasenko:cache_expire_after_write
Closed

Fix EvictableCacheBuilder#expireAfterWrite behavior#14476
atanasenko wants to merge 1 commit intotrinodb:masterfrom
atanasenko:cache_expire_after_write

Conversation

@atanasenko
Copy link
Copy Markdown
Member

@atanasenko atanasenko commented Oct 5, 2022

Description

When .expireAfterWrite() is set, EvictableCache loads the value twice
after expiration. On any read/write operation the cache might call a
maintenance loop which might evict expired items. Evicted items might
include arbitrary set of values, including the one that is currently
being read, in this case a reload of the value will follow eviction.

With this change key tokens for evicted items are not removed on
notification, but instead only marked as expired, while at the end of
operation the currently processed token is restored while all others are
removed.

Release notes

(x) This is not user-visible or docs only and no release notes are required.
( ) Release notes are required, please propose a release note for me.
( ) Release notes are required, with the following suggested text:

Fixes #14545

@atanasenko atanasenko requested a review from findepi October 5, 2022 13:42
@cla-bot cla-bot bot added the cla-signed label Oct 5, 2022
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Will this make this test flaky when there is a GC pause?

@atanasenko atanasenko force-pushed the cache_expire_after_write branch from 915b59c to 07e67c3 Compare October 6, 2022 12:00
@atanasenko atanasenko requested a review from findepi October 6, 2022 12:00
@atanasenko atanasenko force-pushed the cache_expire_after_write branch 2 times, most recently from c24d3ff to a4d5430 Compare October 6, 2022 12:23
@atanasenko
Copy link
Copy Markdown
Member Author

There is still the #asMap() method which returns a live map operations of which might also cause evictions but those are not handled properly. What do you think about setting an additional flag when any of the values are expired, and if the flag is set, force the removal of tokens on access to tokens map instead of doing it after the operation?

@atanasenko atanasenko force-pushed the cache_expire_after_write branch from a4d5430 to 033009e Compare October 6, 2022 12:52
@atanasenko
Copy link
Copy Markdown
Member Author

Actually, disregard that, I went with a simple approach where evicted tokens for the currently loaded value get revived and stored in the map again. There is still a chance that a parallel thread will see the missing token and reload the value, but I think this is acceptable.

Comment on lines 130 to 127
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use Stopwatch -- it's ligheter-weight and easier to lead.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

BTW i wanted to put a PR with a test preventing memory leak (#14476 (comment)) and end-up also writing my version of this test #14502. not sure if this helps

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the timeout until.isAfter(Instant.now()) condition is checked before a call is made, so we should expect allow one more call here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It never goes past the threshold of the last expiration. Or am I missing something?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You don't know how much time elapses between checking the clock and invoking cache.get

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

am i missing something?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i think this boils down to the fact it's much better to use ~Ticker for tests, per @losipiuk #14502 (comment)

@atanasenko atanasenko force-pushed the cache_expire_after_write branch 2 times, most recently from 1433f14 to c331728 Compare October 6, 2022 16:21
When `.expireAfterWrite()` is set, EvictableCache loads the value twice
after expiration. On any read/write operation the cache might call a
maintenance loop which might evict expired items. Evicted items might
include arbitrary set of values, including the one that is currently
being read, in this case a reload of the value will follow eviction.

With this change tokens that got expired are revived before loading them
again.
@atanasenko atanasenko force-pushed the cache_expire_after_write branch from c331728 to 12f5314 Compare October 6, 2022 16:38
@atanasenko
Copy link
Copy Markdown
Member Author

I moved the 'revival' deeper into the loading of the value. It turns out loading is happening after the eviction of expired items, an attempt to read as well as the removal notifications calls.

@atanasenko atanasenko requested a review from findepi October 6, 2022 16:44
Token<K> token = tokens.computeIfAbsent(key, ignored -> newToken);
Callable<? extends V> valueLoaderImpl = () -> {
// revive token if it got expired before reloading
tokens.computeIfAbsent(token.getKey(), ignored -> token);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

would this be equivalent?

Suggested change
tokens.computeIfAbsent(token.getKey(), ignored -> token);
tokens.computeIfAbsent(key, ignored -> token);

Callable<? extends V> valueLoaderImpl = () -> {
// revive token if it got expired before reloading
tokens.computeIfAbsent(token.getKey(), ignored -> token);
return valueLoader.call();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just thinking -- should the revival happen before or after valueLoader.call?
or it doesn't matter?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

am i missing something?

Callable<? extends V> valueLoaderImpl = () -> {
// revive token if it got expired before reloading
tokens.computeIfAbsent(token.getKey(), ignored -> token);
return valueLoader.call();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

related -- if valueLoader throws and this wasn't a new token, we can revive it and leak

@findepi
Copy link
Copy Markdown
Member

findepi commented Oct 14, 2022

I tried addressing it slightly differently #14644

@atanasenko
Copy link
Copy Markdown
Member Author

Superseded by #14644

@atanasenko atanasenko closed this Oct 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

EvictableCache loads value twice after time-based expiration

3 participants