uros-b commented on code in PR #17375:
URL: https://github.com/apache/iceberg/pull/17375#discussion_r3652149746


##########
core/src/main/java/org/apache/iceberg/CachingCatalog.java:
##########
@@ -144,29 +144,41 @@ public Table loadTable(TableIdentifier ident) {
       return cached;
     }
 
-    Table table = tableCache.get(canonicalized, catalog::loadTable);
-
-    if (table instanceof BaseMetadataTable) {
-      // Cache underlying table
-      TableIdentifier originTableIdentifier =
-          TableIdentifier.of(canonicalized.namespace().levels());
-      Table originTable = tableCache.get(originTableIdentifier, 
catalog::loadTable);
-
-      // Share TableOperations instance of origin table for all metadata 
tables, so that metadata
-      // table instances are refreshed as well when origin table instance is 
refreshed.
-      if (originTable instanceof HasTableOperations) {
-        TableOperations ops = ((HasTableOperations) originTable).operations();
-        MetadataTableType type = MetadataTableType.from(canonicalized.name());
-
-        Table metadataTable =
-            MetadataTableUtils.createMetadataTableInstance(
-                ops, catalog.name(), originTableIdentifier, canonicalized, 
type);
-        tableCache.put(canonicalized, metadataTable);
-        return metadataTable;
-      }
-    }
-
-    return table;
+    // Use asMap().compute() atomically so that any concurrent invalidate() 
will not race with a
+    // put that would otherwise resurrect a stale value in the cache. See 
issue #17338.
+    return tableCache
+        .asMap()
+        .compute(
+            canonicalized,
+            (key, existing) -> {
+              if (existing != null) {
+                return existing;
+              }
+
+              Table table = catalog.loadTable(key);
+
+              if (table instanceof BaseMetadataTable) {
+                // For metadata tables, share the TableOperations of the 
origin table so that
+                // metadata table instances are refreshed as well when the 
origin table instance
+                // is refreshed. The origin table is loaded (and cached) via a 
separate atomic
+                // compute below.
+                TableIdentifier originTableIdentifier =
+                    TableIdentifier.of(key.namespace().levels());
+                Table originTable =
+                    tableCache
+                        .asMap()
+                        .computeIfAbsent(originTableIdentifier, 
catalog::loadTable);

Review Comment:
   The metadata branch performs this nested call from inside the outer 
compute(canonicalized, …) remapping function, mutating the same cache during 
its own computation. This is the anti-pattern PR #3801 deliberately removed 
after production deadlock #3791; both Caffeine's asMap() view and the CHM it 
wraps document that the mapping function must not update the map during 
computation. Depending on interleaving and key hashing, it can deadlock or 
throw a "Recursive update" IllegalStateException, and the still-synchronous 
RemovalListener firing invalidateAll(...) during eviction compounds the 
liveness surface. The branch is reachable by any ordinary metadata-table load 
(e.g. table.snapshots, table.files), so this is a common path, not an edge 
case. Merging as-is risks regressing #3791.



-- 
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