-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Conversation
0488f80
to
90760b8
Compare
30d864e
to
63e0f15
Compare
63e0f15
to
c67d35f
Compare
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Outdated
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Outdated
Show resolved
Hide resolved
|
||
public EvictableCache(CacheBuilder<? super K, Object> cacheBuilder) | ||
{ | ||
this.delegate = cacheBuilder.build(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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());
}
?
There was a problem hiding this comment.
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.
There was a problem hiding this 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) | |||
|
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/may/does/
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Outdated
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/cache/TestEvictableCache.java
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/cache/EvictableCache.java
Show resolved
Hide resolved
assertEquals((long) cache.get(key, remoteState::get), remoteState.get()); | ||
} | ||
finally { | ||
executor.shutdownNow(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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]>
c67d35f
to
a10b1e5
Compare
AC |
Thank you! |
The
Cache
used inCachingJdbcClient
was susceptible to a racebetween 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.