前置条件
系统信息
版本:2.23.0
使用的哪种方式运行?
Docker
发生了什么?
通过ai完成了修复,以下是ai给出的结论:
Bug 原因
Halo 2.23.0 升级了 Reactor Core 到 3.8.3,这个版本的 Mono.cacheInvalidateIf() 要求源 Mono 必须产生一个值,如果源完成为空(Mono.empty()),会直接抛出 NoSuchElementException: cacheInvalidateWhen expects a value, source completed empty。
代码中有两处使用了 cacheInvalidateIf,都没有对空源做兜底:
1. DefaultReactiveSettingFetcher(主要触发点)
插件的配置获取器,当插件没有 configMapName 或对应的 ConfigMap 在数据库中不存在时,直接返回 Mono.empty() 给 cacheInvalidateIf,导致异常。前端模板渲染时会调用插件提供的 Thymeleaf finder,触发这个路径。
2. DefaultSystemConfigFetcher(防御性问题)
系统配置获取器,computeSystemConfig() 用 Mono.zip 合并默认配置和覆盖配置。Mono.zip 的特性是任一源为空则整体为空。虽然外层有 defaultIfEmpty(Map.of()),但 getOverrideConfigMap 没有 switchIfEmpty 兜底,在极端竞态条件下可能导致同样的问题。
修复方法
DefaultReactiveSettingFetcher.java:
configMapName 为空时,从 return Mono.empty() 改为 return Mono.just(emptyConfigMap())
client.fetch() 返回空时,加 .switchIfEmpty(Mono.fromSupplier(DefaultReactiveSettingFetcher::emptyConfigMap)) 兜底
DefaultSystemConfigFetcher.java:
- 给
getOverrideConfigMap 和 getDefaultConfigMap 都加上 .switchIfEmpty(...) 返回空数据的 ConfigMap,确保 Mono.zip 不会因为任一源为空而整体完成为空
核心思路就是保证传给 cacheInvalidateIf 的源 Mono 永远不会完成为空。
复现步骤
No response
相关日志输出
附加信息
No response
前置条件
系统信息
版本:2.23.0
使用的哪种方式运行?
Docker
发生了什么?
通过ai完成了修复,以下是ai给出的结论:
Bug 原因
Halo 2.23.0 升级了 Reactor Core 到 3.8.3,这个版本的
Mono.cacheInvalidateIf()要求源 Mono 必须产生一个值,如果源完成为空(Mono.empty()),会直接抛出NoSuchElementException: cacheInvalidateWhen expects a value, source completed empty。代码中有两处使用了
cacheInvalidateIf,都没有对空源做兜底:1.
DefaultReactiveSettingFetcher(主要触发点)插件的配置获取器,当插件没有
configMapName或对应的 ConfigMap 在数据库中不存在时,直接返回Mono.empty()给cacheInvalidateIf,导致异常。前端模板渲染时会调用插件提供的 Thymeleaf finder,触发这个路径。2.
DefaultSystemConfigFetcher(防御性问题)系统配置获取器,
computeSystemConfig()用Mono.zip合并默认配置和覆盖配置。Mono.zip的特性是任一源为空则整体为空。虽然外层有defaultIfEmpty(Map.of()),但getOverrideConfigMap没有switchIfEmpty兜底,在极端竞态条件下可能导致同样的问题。修复方法
DefaultReactiveSettingFetcher.java:configMapName为空时,从return Mono.empty()改为return Mono.just(emptyConfigMap())client.fetch()返回空时,加.switchIfEmpty(Mono.fromSupplier(DefaultReactiveSettingFetcher::emptyConfigMap))兜底DefaultSystemConfigFetcher.java:getOverrideConfigMap和getDefaultConfigMap都加上.switchIfEmpty(...)返回空数据的 ConfigMap,确保Mono.zip不会因为任一源为空而整体完成为空核心思路就是保证传给
cacheInvalidateIf的源 Mono 永远不会完成为空。复现步骤
No response
相关日志输出
附加信息
No response