fightBoxing opened a new pull request, #17375:
URL: https://github.com/apache/iceberg/pull/17375
## Summary
Fix the race condition in `CachingCatalog.loadTable()` as reported in
#17338,其中 `Cache.put` 与 `Cache.invalidate` / `Cache.invalidateAll`
之间存在竞态条件,可能导致陈旧(stale)的表引用被重新放回缓存。
## Race Condition
```
Thread A: 检查缓存 → 空,开始加载新值(此时尚未 stale)
Thread B: 修改外部状态(A 计算的值变为 stale),调用 invalidate()
Thread A: 将 stale 值 put 回缓存 ← ❌ 陈旧值残留在缓存中直到 TTL 过期
```
这在启用 OpenLineage(后台线程调用 `loadTable()`)与写入操作并发时尤其容易被触发。
## Fix
将 `loadTable()` 中原有的三步非原子操作替换为单次 `tableCache.asMap().compute()` 原子调用:
```java
// ❌ 旧代码:getIfPresent + get + put 三步之间存在竞态窗口
Table table = tableCache.get(canonicalized, catalog::loadTable);
if (table instanceof BaseMetadataTable) {
tableCache.put(canonicalized, metadataTable); // 与 invalidate 竞争
}
// ✅ 新代码:asMap().compute() 全原子操作
return tableCache.asMap().compute(canonicalized, (key, existing) -> {
if (existing != null) return existing;
Table table = catalog.loadTable(key);
// ... metadata table 处理也在 compute 内部完成
});
```
`ConcurrentHashMap`(Caffeine 的底层实现)保证同一 key 上的 `compute` 和 `remove`(即
`invalidate`)是串行化的,从而消除了竞态窗口。
## Test
新增回归测试 `testConcurrentLoadAndInvalidateDoesNotResurrectStaleEntry`:
- 使用 `CountDownLatch` 精确同步 `loadTable` 和 `invalidateTable` 同时启动
- 反复 200 次并发碰撞
- 验证每次碰撞后 `loadTable` 始终返回有效引用
所有原有 `TestCachingCatalog` 测试(13 个用例)全部通过,无回归。
## Files Changed
- `core/src/main/java/org/apache/iceberg/CachingCatalog.java` — 重构
loadTable() 使用原子 compute
- `core/src/test/java/org/apache/iceberg/hadoop/TestCachingCatalog.java` —
新增并发回归测试
Closes #17338.
--
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]