github-actions[bot] commented on code in PR #67417:
URL: https://github.com/apache/doris/pull/67417#discussion_r4023377476


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java:
##########
@@ -74,40 +162,303 @@ public MetaCache(String name,
                 maxSize,
                 true,
                 null);
-        namesCache = namesCacheFactory.buildCache(namesCacheLoader, executor);
+        namesCache = namesCacheFactory.buildCache();
         // Use sync removal listener to prevent deadlock (removal listener 
calls invalidateAll)
         // NOTE: This cache should NOT use refreshAfterWrite, as it would 
become synchronous
         metaObjCache = 
objCacheFactory.buildCacheWithSyncRemovalListener(metaObjCacheLoader, 
removalListener);
     }
 
     public List<String> listNames() {
-        return 
Objects.requireNonNull(namesCache.get("")).stream().map(Pair::value).collect(Collectors.toList());
+        return 
getNames(false).stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    public List<String> refreshNames() {
+        throwIfInterrupted();
+        // Retire any active load so the forced refresh is not blocked behind 
a stuck
+        // background refresh. Only advance the generation when the active 
load is still
+        // running (not done); a completed load has already been cleared by 
finishNamesLoad
+        // and cannot publish stale results.
+        synchronized (namesMutationLock) {
+            if (activeNamesLoad != null && !activeNamesLoad.result.isDone()) {
+                advanceNamesGeneration();
+            }
+            activeNamesLoad = null;
+            physicalNamesLoads.clear();
+        }
+        throwIfInterrupted();
+        return 
getNames(true).stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    private void throwIfInterrupted() {
+        if (Thread.currentThread().isInterrupted()) {
+            throw new CompletionException(new InterruptedException());
+        }
+    }
+
+    private List<Pair<String, String>> getNames(boolean forceRefresh) {
+        for (int attempt = 0; attempt < MAX_NAMES_LOAD_ATTEMPTS; attempt++) {
+            if (forceRefresh) {
+                throwIfInterrupted();
+            }
+            NamesCacheValue value = forceRefresh ? null : 
namesCache.getIfPresent("");
+            List<Pair<String, String>> currentNames = null;
+            synchronized (namesMutationLock) {
+                if (value != null && value.complete && value.generation == 
namesGeneration.get()) {
+                    currentNames = value.snapshot();
+                }
+            }
+            if (currentNames != null) {
+                scheduleNamesRefresh(value);
+                return currentNames;
+            }
+            value = loadNames(forceRefresh, true, null);
+            synchronized (namesMutationLock) {
+                if (value != null && value.complete && value.generation == 
namesGeneration.get()) {
+                    return value.snapshot();
+                }
+            }
+        }
+        throw new IllegalStateException("Failed to load names for " + name
+                + " because metadata kept changing");
+    }
+
+    private NamesCacheValue loadNames(boolean forceRefresh, boolean 
awaitActiveLoad, Long expectedGeneration) {
+        NamesLoad namesLoad = null;
+        boolean loadOwner = false;
+        long requestedGeneration;
+        synchronized (namesMutationLock) {
+            if (expectedGeneration != null && expectedGeneration != 
namesGeneration.get()) {
+                return null;
+            }
+            NamesCacheValue cached = namesCache.getIfPresent("");
+            if (!forceRefresh && cached != null && cached.complete
+                    && cached.generation == namesGeneration.get()) {
+                return cached;
+            }
+            long loadGeneration = namesGeneration.get();
+            if (activeNamesLoad != null && activeNamesLoad.generation == 
loadGeneration) {
+                if (!awaitActiveLoad) {
+                    return null;
+                }
+                namesLoad = activeNamesLoad;
+            }
+            requestedGeneration = loadGeneration;
+        }
+
+        if (namesLoad != null) {
+            return awaitNamesLoad(namesLoad);
+        }
+
+        // Lifecycle admission may acquire the catalog monitor. Keep it 
outside the names
+        // mutation lock because catalog reset advances the names generation 
under that monitor.
+        long loadEpoch = namesLoadEpochSupplier.getAsLong();
+        synchronized (namesMutationLock) {
+            if (requestedGeneration != namesGeneration.get()
+                    || expectedGeneration != null && expectedGeneration != 
namesGeneration.get()) {
+                return null;
+            }
+            NamesCacheValue cached = namesCache.getIfPresent("");
+            if (!forceRefresh && cached != null && cached.complete
+                    && cached.generation == requestedGeneration) {
+                return cached;
+            }
+            if (activeNamesLoad != null && activeNamesLoad.generation == 
requestedGeneration) {
+                if (!awaitActiveLoad) {
+                    return null;
+                }
+                namesLoad = activeNamesLoad;
+            } else {
+                if (physicalNamesLoads.size() >= MAX_PHYSICAL_NAMES_LOADS) {
+                    return null;
+                }
+                Map<String, Pair<String, String>> incompleteNames = cached != 
null && !cached.complete
+                        ? Maps.newLinkedHashMap(cached.names) : 
Maps.newLinkedHashMap();
+                namesLoad = new NamesLoad(requestedGeneration, loadEpoch, 
incompleteNames);
+                activeNamesLoad = namesLoad;
+                physicalNamesLoads.add(namesLoad);
+                loadOwner = true;
+            }
+        }
+
+        if (!loadOwner) {
+            return awaitNamesLoad(namesLoad);
+        }
+
+        try {
+            List<Pair<String, String>> loadedNames = 
Objects.requireNonNull(namesCacheLoader.load(""));
+            NamesCacheValue value = null;
+            synchronized (namesMutationLock) {
+                if (namesLoad.generation == namesGeneration.get()
+                        && namesLoad.generation >= minimumLoadGeneration
+                        && namesLoadEpochValidator.test(namesLoad.loadEpoch)) {
+                    Map<String, Pair<String, String>> names = 
toNamesMap(loadedNames);
+                    names.putAll(namesLoad.incompleteNames);
+                    value = new NamesCacheValue(namesGeneration.get(), names, 
true);
+                    namesCache.put("", value);
+                    publishNames(value);
+                }
+            }
+            finishNamesLoad(namesLoad);
+            namesLoad.result.complete(value);
+            return value;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            CompletionException failure = new CompletionException(e);
+            finishNamesLoad(namesLoad);
+            namesLoad.result.completeExceptionally(failure);
+            throw failure;
+        } catch (RuntimeException e) {
+            finishNamesLoad(namesLoad);
+            if (!namesLoad.result.completeExceptionally(e)) {
+                return null;
+            }
+            throw e;
+        } catch (Error e) {
+            finishNamesLoad(namesLoad);
+            namesLoad.result.completeExceptionally(e);
+            throw e;
+        } catch (Exception e) {
+            CompletionException failure = new CompletionException(e);
+            finishNamesLoad(namesLoad);
+            if (!namesLoad.result.completeExceptionally(failure)) {
+                return null;
+            }
+            throw failure;
+        } finally {
+            finishNamesLoad(namesLoad);
+        }
+    }
+
+    private void finishNamesLoad(NamesLoad namesLoad) {
+        synchronized (namesMutationLock) {
+            if (activeNamesLoad == namesLoad) {
+                activeNamesLoad = null;
+            }
+            physicalNamesLoads.remove(namesLoad);
+        }
+    }
+
+    private NamesCacheValue awaitNamesLoad(NamesLoad namesLoad) {
+        try {
+            return namesLoad.result.get();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new CompletionException(e);
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            if (cause instanceof Error) {
+                throw (Error) cause;
+            }
+            throw new CompletionException(cause);
+        }
+    }
+
+    private Map<String, Pair<String, String>> toNamesMap(List<Pair<String, 
String>> names) {
+        Map<String, Pair<String, String>> namesMap = Maps.newLinkedHashMap();
+        for (Pair<String, String> pair : names) {
+            namesMap.put(pair.value(), pair);
+        }
+        return namesMap;
+    }
+
+    private void scheduleNamesRefresh(NamesCacheValue value) {
+        if (System.nanoTime() - value.writeNanos < 
namesRefreshAfterWriteNanos) {
+            return;
+        }
+        scheduleNamesRefresh(value.generation);
+    }
+
+    private void scheduleNamesRefresh(long generation) {
+        NamesRefresh refresh;
+        synchronized (namesMutationLock) {
+            if (generation != namesGeneration.get()
+                    || activeNamesRefreshes.containsKey(generation)) {
+                return;
+            }
+            if (activeNamesRefreshes.size() >= MAX_NAMES_REFRESH_FLIGHTS) {
+                pendingNamesRefreshGeneration = generation;
+                return;
+            }
+            refresh = new NamesRefresh(generation);
+            activeNamesRefreshes.put(generation, refresh);
+            if (pendingNamesRefreshGeneration != null && 
pendingNamesRefreshGeneration == generation) {
+                pendingNamesRefreshGeneration = null;
+            }
+        }
+        try {
+            namesRefreshExecutor.execute(() -> {
+                try {
+                    loadNames(true, false, refresh.generation);
+                } catch (Exception e) {
+                    LOG.warn("Failed to refresh names cache for {}", name, e);
+                } finally {
+                    clearNamesRefresh(refresh);
+                }
+            });
+        } catch (RuntimeException e) {
+            clearNamesRefresh(refresh);
+            LOG.warn("Failed to schedule names cache refresh for {}", name, e);
+        }
+    }
+
+    private void clearNamesRefresh(NamesRefresh refresh) {
+        Long pendingGeneration = null;
+        synchronized (namesMutationLock) {
+            if (activeNamesRefreshes.get(refresh.generation) == refresh) {
+                activeNamesRefreshes.remove(refresh.generation);
+                if (pendingNamesRefreshGeneration != null) {
+                    pendingGeneration = pendingNamesRefreshGeneration;
+                    pendingNamesRefreshGeneration = null;
+                }
+            }
+        }
+        if (pendingGeneration != null) {
+            scheduleNamesRefresh(pendingGeneration);
+        }
     }
 
     public String getRemoteName(String localName) {
-        return Objects.requireNonNull(namesCache.getIfPresent("")).stream()
+        NamesCacheValue value = namesCache.getIfPresent("");
+        synchronized (namesMutationLock) {
+            if (value != null && value.generation == namesGeneration.get()) {
+                Pair<String, String> pair = value.names.get(localName);
+                if (pair != null) {
+                    return pair.key();
+                }
+                if (value.complete) {
+                    return null;
+                }
+            }
+        }
+        return getNames(false).stream()
                 .filter(pair -> pair.value().equals(localName))
                 .map(Pair::key)
                 .findFirst()
                 .orElse(null);
     }
 
+    private void publishNames(NamesCacheValue value) {
+        namesCacheUpdateAction.accept(value.snapshot());
+    }
+
     public Optional<T> getMetaObj(String name, long id) {
         Optional<T> val = metaObjCache.getIfPresent(name);
-        if (val == null || !val.isPresent()) {
-            synchronized (metaObjCache) {
-                val = metaObjCache.getIfPresent(name);
-                if (val != null && val.isPresent()) {
-                    return val;
-                }
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("trigger getMetaObj in metacache {}, obj name: 
{}, id: {}",
-                            this.name, name, id, new Exception());
-                }
-                metaObjCache.invalidate(name);
-                val = metaObjCache.get(name);
-                idToName.put(id, name);
-            }
+        if (val != null && val.isPresent()) {
+            return val;
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("trigger getMetaObj in metacache {}, obj name: {}, id: 
{}",
+                    this.name, name, id, new Exception());
+        }
+        // Do not hold a cache-wide monitor during the blocking loader call
+        // (buildDbForInit → connector I/O). Caffeine serializes same-key 
loads internally;
+        // different keys can load in parallel without blocking 
updateCache/invalidate.
+        val = metaObjCache.get(name);
+        if (val != null && val.isPresent()) {
+            idToName.put(id, name);

Review Comment:
   [P2] Preserve the ID route after an empty load
   
   This guard also moves the old unconditional `idToName.put(id, name)` behind 
object presence. If a name-based load returns empty, then that negative entry 
expires or is evicted and the remote object appears, `getMetaObjById(id)` still 
returns before invoking the loader because the deterministic ID-to-name route 
was never recorded. That breaks the public ID-form database/table lookups and 
replay even after the cached miss is gone. This is separate from the 
cached-empty retry above, which only helps callers that still have the name. 
Please retain the supplied ID/name association after a load attempt (while 
continuing to remove it on explicit invalidation), and cover miss → 
expiry/eviction → ID-only recovery.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java:
##########
@@ -74,40 +162,303 @@ public MetaCache(String name,
                 maxSize,
                 true,
                 null);
-        namesCache = namesCacheFactory.buildCache(namesCacheLoader, executor);
+        namesCache = namesCacheFactory.buildCache();
         // Use sync removal listener to prevent deadlock (removal listener 
calls invalidateAll)
         // NOTE: This cache should NOT use refreshAfterWrite, as it would 
become synchronous
         metaObjCache = 
objCacheFactory.buildCacheWithSyncRemovalListener(metaObjCacheLoader, 
removalListener);
     }
 
     public List<String> listNames() {
-        return 
Objects.requireNonNull(namesCache.get("")).stream().map(Pair::value).collect(Collectors.toList());
+        return 
getNames(false).stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    public List<String> refreshNames() {
+        throwIfInterrupted();
+        // Retire any active load so the forced refresh is not blocked behind 
a stuck
+        // background refresh. Only advance the generation when the active 
load is still
+        // running (not done); a completed load has already been cleared by 
finishNamesLoad
+        // and cannot publish stale results.
+        synchronized (namesMutationLock) {
+            if (activeNamesLoad != null && !activeNamesLoad.result.isDone()) {
+                advanceNamesGeneration();
+            }
+            activeNamesLoad = null;
+            physicalNamesLoads.clear();
+        }
+        throwIfInterrupted();
+        return 
getNames(true).stream().map(Pair::value).collect(Collectors.toList());
+    }
+
+    private void throwIfInterrupted() {
+        if (Thread.currentThread().isInterrupted()) {
+            throw new CompletionException(new InterruptedException());
+        }
+    }
+
+    private List<Pair<String, String>> getNames(boolean forceRefresh) {
+        for (int attempt = 0; attempt < MAX_NAMES_LOAD_ATTEMPTS; attempt++) {
+            if (forceRefresh) {
+                throwIfInterrupted();
+            }
+            NamesCacheValue value = forceRefresh ? null : 
namesCache.getIfPresent("");
+            List<Pair<String, String>> currentNames = null;
+            synchronized (namesMutationLock) {
+                if (value != null && value.complete && value.generation == 
namesGeneration.get()) {
+                    currentNames = value.snapshot();
+                }
+            }
+            if (currentNames != null) {
+                scheduleNamesRefresh(value);
+                return currentNames;
+            }
+            value = loadNames(forceRefresh, true, null);
+            synchronized (namesMutationLock) {
+                if (value != null && value.complete && value.generation == 
namesGeneration.get()) {
+                    return value.snapshot();
+                }
+            }
+        }
+        throw new IllegalStateException("Failed to load names for " + name
+                + " because metadata kept changing");
+    }
+
+    private NamesCacheValue loadNames(boolean forceRefresh, boolean 
awaitActiveLoad, Long expectedGeneration) {
+        NamesLoad namesLoad = null;
+        boolean loadOwner = false;
+        long requestedGeneration;
+        synchronized (namesMutationLock) {
+            if (expectedGeneration != null && expectedGeneration != 
namesGeneration.get()) {
+                return null;
+            }
+            NamesCacheValue cached = namesCache.getIfPresent("");
+            if (!forceRefresh && cached != null && cached.complete
+                    && cached.generation == namesGeneration.get()) {
+                return cached;
+            }
+            long loadGeneration = namesGeneration.get();
+            if (activeNamesLoad != null && activeNamesLoad.generation == 
loadGeneration) {
+                if (!awaitActiveLoad) {
+                    return null;
+                }
+                namesLoad = activeNamesLoad;
+            }
+            requestedGeneration = loadGeneration;
+        }
+
+        if (namesLoad != null) {
+            return awaitNamesLoad(namesLoad);
+        }
+
+        // Lifecycle admission may acquire the catalog monitor. Keep it 
outside the names
+        // mutation lock because catalog reset advances the names generation 
under that monitor.
+        long loadEpoch = namesLoadEpochSupplier.getAsLong();
+        synchronized (namesMutationLock) {
+            if (requestedGeneration != namesGeneration.get()
+                    || expectedGeneration != null && expectedGeneration != 
namesGeneration.get()) {
+                return null;
+            }
+            NamesCacheValue cached = namesCache.getIfPresent("");
+            if (!forceRefresh && cached != null && cached.complete
+                    && cached.generation == requestedGeneration) {
+                return cached;
+            }
+            if (activeNamesLoad != null && activeNamesLoad.generation == 
requestedGeneration) {
+                if (!awaitActiveLoad) {
+                    return null;
+                }
+                namesLoad = activeNamesLoad;
+            } else {
+                if (physicalNamesLoads.size() >= MAX_PHYSICAL_NAMES_LOADS) {
+                    return null;
+                }
+                Map<String, Pair<String, String>> incompleteNames = cached != 
null && !cached.complete
+                        ? Maps.newLinkedHashMap(cached.names) : 
Maps.newLinkedHashMap();
+                namesLoad = new NamesLoad(requestedGeneration, loadEpoch, 
incompleteNames);
+                activeNamesLoad = namesLoad;
+                physicalNamesLoads.add(namesLoad);
+                loadOwner = true;
+            }
+        }
+
+        if (!loadOwner) {
+            return awaitNamesLoad(namesLoad);
+        }
+
+        try {
+            List<Pair<String, String>> loadedNames = 
Objects.requireNonNull(namesCacheLoader.load(""));
+            NamesCacheValue value = null;
+            synchronized (namesMutationLock) {
+                if (namesLoad.generation == namesGeneration.get()
+                        && namesLoad.generation >= minimumLoadGeneration
+                        && namesLoadEpochValidator.test(namesLoad.loadEpoch)) {
+                    Map<String, Pair<String, String>> names = 
toNamesMap(loadedNames);
+                    names.putAll(namesLoad.incompleteNames);
+                    value = new NamesCacheValue(namesGeneration.get(), names, 
true);
+                    namesCache.put("", value);
+                    publishNames(value);
+                }
+            }
+            finishNamesLoad(namesLoad);
+            namesLoad.result.complete(value);
+            return value;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            CompletionException failure = new CompletionException(e);
+            finishNamesLoad(namesLoad);
+            namesLoad.result.completeExceptionally(failure);
+            throw failure;
+        } catch (RuntimeException e) {
+            finishNamesLoad(namesLoad);
+            if (!namesLoad.result.completeExceptionally(e)) {
+                return null;
+            }
+            throw e;
+        } catch (Error e) {
+            finishNamesLoad(namesLoad);
+            namesLoad.result.completeExceptionally(e);
+            throw e;
+        } catch (Exception e) {
+            CompletionException failure = new CompletionException(e);
+            finishNamesLoad(namesLoad);
+            if (!namesLoad.result.completeExceptionally(failure)) {
+                return null;
+            }
+            throw failure;
+        } finally {
+            finishNamesLoad(namesLoad);
+        }
+    }
+
+    private void finishNamesLoad(NamesLoad namesLoad) {
+        synchronized (namesMutationLock) {
+            if (activeNamesLoad == namesLoad) {
+                activeNamesLoad = null;
+            }
+            physicalNamesLoads.remove(namesLoad);
+        }
+    }
+
+    private NamesCacheValue awaitNamesLoad(NamesLoad namesLoad) {
+        try {
+            return namesLoad.result.get();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new CompletionException(e);
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            if (cause instanceof Error) {
+                throw (Error) cause;
+            }
+            throw new CompletionException(cause);
+        }
+    }
+
+    private Map<String, Pair<String, String>> toNamesMap(List<Pair<String, 
String>> names) {
+        Map<String, Pair<String, String>> namesMap = Maps.newLinkedHashMap();
+        for (Pair<String, String> pair : names) {
+            namesMap.put(pair.value(), pair);
+        }
+        return namesMap;
+    }
+
+    private void scheduleNamesRefresh(NamesCacheValue value) {
+        if (System.nanoTime() - value.writeNanos < 
namesRefreshAfterWriteNanos) {
+            return;
+        }
+        scheduleNamesRefresh(value.generation);
+    }
+
+    private void scheduleNamesRefresh(long generation) {
+        NamesRefresh refresh;
+        synchronized (namesMutationLock) {
+            if (generation != namesGeneration.get()
+                    || activeNamesRefreshes.containsKey(generation)) {
+                return;
+            }
+            if (activeNamesRefreshes.size() >= MAX_NAMES_REFRESH_FLIGHTS) {
+                pendingNamesRefreshGeneration = generation;
+                return;
+            }
+            refresh = new NamesRefresh(generation);
+            activeNamesRefreshes.put(generation, refresh);
+            if (pendingNamesRefreshGeneration != null && 
pendingNamesRefreshGeneration == generation) {
+                pendingNamesRefreshGeneration = null;
+            }
+        }
+        try {
+            namesRefreshExecutor.execute(() -> {
+                try {
+                    loadNames(true, false, refresh.generation);
+                } catch (Exception e) {
+                    LOG.warn("Failed to refresh names cache for {}", name, e);
+                } finally {
+                    clearNamesRefresh(refresh);
+                }
+            });
+        } catch (RuntimeException e) {
+            clearNamesRefresh(refresh);
+            LOG.warn("Failed to schedule names cache refresh for {}", name, e);
+        }
+    }
+
+    private void clearNamesRefresh(NamesRefresh refresh) {
+        Long pendingGeneration = null;
+        synchronized (namesMutationLock) {
+            if (activeNamesRefreshes.get(refresh.generation) == refresh) {
+                activeNamesRefreshes.remove(refresh.generation);
+                if (pendingNamesRefreshGeneration != null) {
+                    pendingGeneration = pendingNamesRefreshGeneration;
+                    pendingNamesRefreshGeneration = null;
+                }
+            }
+        }
+        if (pendingGeneration != null) {
+            scheduleNamesRefresh(pendingGeneration);
+        }
     }
 
     public String getRemoteName(String localName) {
-        return Objects.requireNonNull(namesCache.getIfPresent("")).stream()
+        NamesCacheValue value = namesCache.getIfPresent("");
+        synchronized (namesMutationLock) {
+            if (value != null && value.generation == namesGeneration.get()) {
+                Pair<String, String> pair = value.names.get(localName);
+                if (pair != null) {
+                    return pair.key();
+                }
+                if (value.complete) {
+                    return null;
+                }
+            }
+        }
+        return getNames(false).stream()
                 .filter(pair -> pair.value().equals(localName))
                 .map(Pair::key)
                 .findFirst()
                 .orElse(null);
     }
 
+    private void publishNames(NamesCacheValue value) {
+        namesCacheUpdateAction.accept(value.snapshot());
+    }
+
     public Optional<T> getMetaObj(String name, long id) {
         Optional<T> val = metaObjCache.getIfPresent(name);
-        if (val == null || !val.isPresent()) {
-            synchronized (metaObjCache) {
-                val = metaObjCache.getIfPresent(name);
-                if (val != null && val.isPresent()) {
-                    return val;
-                }
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("trigger getMetaObj in metacache {}, obj name: 
{}, id: {}",
-                            this.name, name, id, new Exception());
-                }
-                metaObjCache.invalidate(name);
-                val = metaObjCache.get(name);
-                idToName.put(id, name);
-            }
+        if (val != null && val.isPresent()) {
+            return val;
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("trigger getMetaObj in metacache {}, obj name: {}, id: 
{}",
+                    this.name, name, id, new Exception());
+        }
+        // Do not hold a cache-wide monitor during the blocking loader call
+        // (buildDbForInit → connector I/O). Caffeine serializes same-key 
loads internally;
+        // different keys can load in parallel without blocking 
updateCache/invalidate.
+        val = metaObjCache.get(name);

Review Comment:
   [P1] Preserve retries for cached empty object entries
   
   When `getIfPresent()` returns `Optional.empty()`, this 
`LoadingCache.get(name)` returns that already-present empty value without 
invoking the loader. Both catalog and table loaders can cache empty after a 
transient miss or construction failure, so an object created later remains 
inaccessible for the expire-after-access lifetime, and repeated lookups can 
keep the negative entry alive indefinitely. The unchanged empty-entry tests in 
`MetaCacheTest` also require one reload and now deterministically fail. This is 
distinct from the existing unsynchronized-invalidation race: removing 
invalidation entirely lost sequential retry semantics. Please treat null/empty 
as a miss inside one per-key atomic load so concurrent callers retry once 
without deleting another owner's value.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to