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

Fix invalidation race in CachingJdbcClient #10544

Merged
merged 3 commits into from
Jan 13, 2022

Conversation

findepi
Copy link
Member

@findepi findepi commented Jan 11, 2022

The Cache used in CachingJdbcClient was susceptible to a race
between load-in-flight and invalidation. The invalidation of a key would
not prevent load in flight from being inserted into the cache.

For #10512, other caches need to follow.

@findepi findepi requested review from losipiuk and kokosing January 11, 2022 16:20
@cla-bot cla-bot bot added the cla-signed label Jan 11, 2022
@findepi findepi force-pushed the findepi/better-cache branch from 0488f80 to 90760b8 Compare January 11, 2022 16:20
@findepi findepi changed the title Fix invalidating race in CachingJdbcClient Fix invalidation race in CachingJdbcClient Jan 11, 2022
@findepi findepi force-pushed the findepi/better-cache branch 5 times, most recently from 30d864e to 63e0f15 Compare January 11, 2022 19:59
@findepi findepi force-pushed the findepi/better-cache branch from 63e0f15 to c67d35f Compare January 12, 2022 08:41

public EvictableCache(CacheBuilder<? super K, Object> cacheBuilder)
{
this.delegate = cacheBuilder.build();
Copy link
Member

Choose a reason for hiding this comment

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

Should you assert that delegate is not a LoadingCache?

Copy link
Member Author

Choose a reason for hiding this comment

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

you get LoadingCache from builder.build(loader) right?

public void testInvalidateOngoingLoad(Invalidation invalidation)
throws Exception
{
Cache<Integer, String> cache = EvictableCache.buildWith(CacheBuilder.newBuilder());
Copy link
Member

Choose a reason for hiding this comment

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

Add

    public static <K, V> EvictableCache<K, V> build()
    {
        return new EvictableCache<>(CacheBuilder.newBuilder());
    }

?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's not a useful cache, as it would be unbounded, so i won't add it in EvictableCache.

In the test itself, it doesn't matter.

Copy link
Member

@losipiuk losipiuk left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -91,6 +91,8 @@ public void setTokenExchangeError(String authIdHash, String message)

Copy link
Member

Choose a reason for hiding this comment

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

What about:

  • MongoSession
  • CachingHiveMetastore
  • CachingJdbcClient
    ?

Copy link
Member Author

Choose a reason for hiding this comment

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

MongoSession

covered here

CachingHiveMetastore

tbd

CachingJdbcClient

covered here

@@ -91,6 +91,8 @@ public void setTokenExchangeError(String authIdHash, String message)

public void dropToken(UUID authId)
{
// TODO this may not invalidate ongoing loads (https://github.com/trinodb/trino/issues/10512, https://github.com/google/guava/issues/1881).
Copy link
Member

Choose a reason for hiding this comment

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

s/may/does/

Copy link
Member Author

Choose a reason for hiding this comment

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

Guava can change. I want the reader to be thoughtful, not take my words for granted.

* A Cache implementation similar to ones produced by {@code com.google.common.cache.CacheBuilder},
* but one that does not exhibits <a href="https://github.com/google/guava/issues/1881">Guava issue #1881</a>, i.e.
* a {@link #getIfPresent(Object)} after {@link #invalidate(Object)} is guaranteed to return {@code null} and
* {@link #get(Object, Callable)} after {@link #invalidate(Object)} is guaranteed to load a fresh value.
Copy link
Member

Choose a reason for hiding this comment

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

Please mention about invalidateAll. And mention that this cache will return a key for object that for which load is in-flight.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is only meant as examples, not full log of differences.

assertEquals((long) cache.get(key, remoteState::get), remoteState.get());
}
finally {
executor.shutdownNow();
Copy link
Member

Choose a reason for hiding this comment

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

I would keep executor as a field, that way each test would be less code would be less polluted.

Copy link
Member Author

@findepi findepi Jan 12, 2022

Choose a reason for hiding this comment

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

since threads are paramount here, i'd prefer the tests not to share the executor

Optional<JdbcTableHandle> tableHandle = delegate.getTableHandle(session, schemaTableName);
if (tableHandle.isPresent() || cacheMissing) {
tableHandleCache.put(key, tableHandle);
if (cacheMissing || cachedTableHandle.isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

I would move this to different preparatory commit.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's related.

@@ -547,7 +545,7 @@ CacheStats getStatisticsCacheStats()

private static <T, V> void invalidateCache(Cache<T, V> cache, Predicate<T> filterFunction)
Copy link
Member

Choose a reason for hiding this comment

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

simply pass EvictableCache as argument here

Copy link
Member Author

Choose a reason for hiding this comment

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

You mean to remove the cast?
I want to return the code back to interface-based only, when TODO in io.trino.plugin.base.cache.EvictableCache#asMap is resolved.

findepi and others added 2 commits January 12, 2022 18:11
The `Cache` used in `CachingJdbcClient` was susceptible to a race
between load-in-flight and invalidation. The invalidation of a key would
not prevent load in flight from inserting an already stale value into
the cache.

The `TestCachingJdbcClient` test is courtesy by kokosing.

Co-authored-by: Grzegorz Kokosiński <[email protected]>
@findepi findepi force-pushed the findepi/better-cache branch from c67d35f to a10b1e5 Compare January 12, 2022 17:12
@findepi
Copy link
Member Author

findepi commented Jan 12, 2022

AC

@findepi findepi merged commit 326548d into trinodb:master Jan 13, 2022
@findepi findepi deleted the findepi/better-cache branch January 13, 2022 08:12
@kokosing
Copy link
Member

Thank you!

@github-actions github-actions bot added this to the 369 milestone Jan 13, 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.

3 participants