fix: prevent duplicate ObjectReader and ObjectWriter creation on concurrent cache misses - #7774
fix: prevent duplicate ObjectReader and ObjectWriter creation on concurrent cache misses#7774linw-bai wants to merge 12 commits into
Conversation
91293c9 to
fac67db
Compare
fac67db to
7ab89f3
Compare
wenshao
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): This PR adds per-class ClassValue-based locks in ObjectRe...: none — no check was left unfinished at the ceiling.; This PR adds per-class ClassValue-based locks in ObjectRe...: none — all planned checks completed. (One unverifiable item is embedded in finding 1: whether java.lang.ClassValue exists in the Android SDK at minSdk 26, whi…; This PR adds per-class ClassValue-based locks in ObjectRe...: none — all checks I started were completed within budget..
中文说明
未探索到全部深度(达到工具调用预算):This PR adds per-class ClassValue-based locks in ObjectRe...:none — no check was left unfinished at the ceiling.;This PR adds per-class ClassValue-based locks in ObjectRe...:none — all planned checks completed. (One unverifiable item is embedded in finding 1: whether java.lang.ClassValue exists in the Android SDK at minSdk 26, whi…;This PR adds per-class ClassValue-based locks in ObjectRe...:none — all checks I started were completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.10)
wenshao
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "agent 1a": 无; "agent 6c": none — no check was cut short..
中文说明
未探索到全部深度(达到工具调用预算):"agent 1a":无;"agent 6c":none — no check was cut short.。
— qwen3.8-max via Qwen Code /review (v0.21.11)
wenshao
left a comment
There was a problem hiding this comment.
Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:
core/src/test/java/com/alibaba/fastjson2/writer/ObjectWriterProviderConcurrentTest.java:341 (+1 locations) — [review] unused threadCount locals + unread createCount result fields in both provider concurrent tests (reader twin included)
中文说明
收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.21.11)
wenshao
left a comment
There was a problem hiding this comment.
Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:
core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java:1162 (+2 locations) — [probe] Error-type creator failure path has no test — narrowing the catch to RuntimeException survives the whole suite (writer twin identical)core/src/test/java/com/alibaba/fastjson2/writer/ObjectWriterProviderConcurrentTest.java:419 (+2 locations) — [probe] testParameterizedTypesCreateConcurrently discards returned codecs — a raw-Class-keyed publish/lookup mutant survives (reade…
Convergence: round 5 posted 5 inline comment(s), 5 of them reported for the first time; the previous round posted 12. Findings keep coming back to the same files: core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterProvider.java (findings in rounds 3, 4; 1 more now); core/src/main/java/com/alibaba/fastjson2/internal/CodecCreationCoordinator.java (findings in round 4; 1 more now); core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java (findings in round 4; 1 more now), and 2 more file(s). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 5 轮发布了 5 条行内评论,其中 5 条是首次提出;上一轮发布了 12 条。发现反复回到同一批文件:core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterProvider.java(第 3、4 轮已出过发现,本轮又有 1 条);core/src/main/java/com/alibaba/fastjson2/internal/CodecCreationCoordinator.java(第 4 轮已出过发现,本轮又有 1 条);core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java(第 4 轮已出过发现,本轮又有 1 条),另有 2 个文件。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.0)
Rate-limit timed fallback admissions atomically without blocking behind older fallbacks. Track every active fallback for cycle detection and let queued readers and writers reuse published cache values. Add external-dependency, stale-deadline, multi-owner cycle and cache-publication regressions. Validated core default/reflect suites, JDK 8/11 concurrency tests and 20 coordinator repetitions.
Motivation
When multiple threads access the same uncached JavaBean concurrently, every thread may create its own
ObjectReaderorObjectWriterbefore one instance is published byputIfAbsent.putIfAbsentguarantees that only one instance is retained, but the losing instances have already created their property accessors. This can produce many unnecessary hidden Lambda classes during the first concurrent serialization or deserialization burst.For a bean with two getters and 100 concurrent first-time serializations, fastjson2 2.0.63 loaded 200
RuleEntity$$Lambdaclasses instead of the expected 2. Pre-warmingObjectReaderdoes not help serialization becausetoJSONStringusesObjectWriterProvider.Changes
Type.tryLock; if nested user extension code creates a cyclic dependency, the caller falls back to the original create-and-putIfAbsentbehavior instead of waiting indefinitely.ClassValueto preserve Android API 26 compatibility.Tests
The concurrent regression tests cover both providers and verify:
For the original two-getter reproduction with 100 concurrent first-time serializations:
RuleEntity$$Lambdaclasses loadedObjectReaderonlyObjectWriterThe following commands pass after the change:
The Android Gradle test was not run locally because no Android SDK is installed. The unconditional
ClassValuedependency has been removed; the replacement uses APIs available before the project's minSdk 26.中文
动机
多个线程并发首次访问同一个尚未缓存的 JavaBean 时,每个线程都可能在某个实例通过
putIfAbsent发布到缓存之前,分别创建自己的ObjectReader或ObjectWriter。putIfAbsent只能保证最终缓存中保留一个实例,其他竞争失败的实例已经创建了属性访问器,因此首次并发序列化或反序列化时可能产生大量无用的 Lambda 隐藏类。对于包含两个 getter 的 Bean,使用 100 个线程并发首次序列化时,fastjson2 2.0.63 会加载 200 个
RuleEntity$$Lambda类,而正常只需要 2 个。预热ObjectReader无法规避序列化问题,因为toJSONString使用的是ObjectWriterProvider。改动
Type划分的锁协调通用 reader 和 writer 创建。tryLock;如果用户扩展代码的嵌套创建形成循环依赖,则回退到原来的创建并putIfAbsent行为,而不是无限等待。ClassValue,保持 Android API 26 兼容性。测试
Reader 和 Writer 的并发回归测试均覆盖:
对于原始的双 getter Bean,使用 100 个线程并发首次序列化:
RuleEntity$$Lambda类数ObjectReaderObjectWriter应用修复后,以下命令均执行通过:
本机没有安装 Android SDK,因此未运行 Android Gradle 测试。无条件使用的
ClassValue已移除,替代实现使用的 API 均早于项目的 minSdk 26。