fix(java): support TreeSet/TreeMap subclasses without Comparator constructor in SortedSet/SortedMapSerializer#3344
Merged
chaokunyang merged 2 commits intoapache:mainfrom Feb 17, 2026
Conversation
db9b3e4 to
95a5eed
Compare
chaokunyang
reviewed
Feb 17, 2026
java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionSerializers.java
Outdated
Show resolved
Hide resolved
Collaborator
|
It seems that the comparator is null if the treeset subclass don't pass comparator to parent class. In such cases, treeset Or treemap will just use key compareTo api for sort. So it's not a bug, could you remove the log and keep constructor branch. And could you ask check TreeMap? I think it has similar issue |
… TreeSet subclasses
… TreeMap subclasses
95a5eed to
ecf8f6d
Compare
Contributor
Author
|
Thanks, pushed some changes, have a look again @chaokunyang! |
This was referenced Mar 26, 2026
[Java] ObjectStreamSerializer async meta-shared layer JIT fails for TreeSet/TreeMap subclasses
#3515
Open
mandrean
added a commit
to mandrean/fory
that referenced
this pull request
Mar 26, 2026
…classes Adds optimized child-serializers for subclasses of TreeSet, ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap. Previously these fell back to JDK-compatible serialization because the hierarchy walk hit writeObject/readObject on the parent JDK type. The new serializers handle comparator preservation, slot field serialization, and ref-tracking (including the deferred-ref pattern for concurrent variants). Constructor discovery is centralized in ContainerConstructors, which probes for all standard constructor shapes and gracefully falls through to natural ordering when no comparator-preserving constructor is available (per apache#3344 (comment)). Existing SortedSetSerializer, SortedMapSerializer, and PriorityQueueSerializer are refactored to reuse the same factory logic.
mandrean
added a commit
to mandrean/fory
that referenced
this pull request
Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes: - (Comparator), (SortedSet)/(SortedMap) preserve comparator - (), (Collection)/(Map), (int) use natural ordering - Unsupported constructors and custom-serialized children fall back - Async compilation path verified - InternalComparatorTreeSet verifies the graceful fall-through from apache#3344 (comment) Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests for the existing SortedSetSerializer and SortedMapSerializer.
mandrean
added a commit
to mandrean/fory
that referenced
this pull request
Mar 26, 2026
…classes Adds optimized child-serializers for subclasses of TreeSet, ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap. Previously these fell back to JDK-compatible serialization because the hierarchy walk hit writeObject/readObject on the parent JDK type. The new serializers handle comparator preservation, slot field serialization, and ref-tracking (including the deferred-ref pattern for concurrent variants). Constructor discovery is centralized in ContainerConstructors, which probes for all standard constructor shapes and gracefully falls through to natural ordering when no comparator-preserving constructor is available (per apache#3344 (comment)). Existing SortedSetSerializer, SortedMapSerializer, and PriorityQueueSerializer are refactored to reuse the same factory logic.
mandrean
added a commit
to mandrean/fory
that referenced
this pull request
Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes: - (Comparator), (SortedSet)/(SortedMap) preserve comparator - (), (Collection)/(Map), (int) use natural ordering - Unsupported constructors and custom-serialized children fall back - Async compilation path verified - InternalComparatorTreeSet verifies the graceful fall-through from apache#3344 (comment) Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests for the existing SortedSetSerializer and SortedMapSerializer.
mandrean
added a commit
to mandrean/fory
that referenced
this pull request
Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes: - (Comparator), (SortedSet)/(SortedMap) preserve comparator - (), (Collection)/(Map), (int) use natural ordering - Unsupported constructors and custom-serialized children fall back - Async compilation path verified - InternalComparatorTreeSet verifies the graceful fall-through from apache#3344 (comment) Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests for the existing SortedSetSerializer and SortedMapSerializer.
chaokunyang
added a commit
that referenced
this pull request
Mar 27, 2026
## Why? As a follow-up to #3342 and #3344, I wanted to make sure there is test coverage for all the public constructors of the different Map/Set classes supported by the serializers. That exposed a copy-path bug in `ConcurrentSkipListSetSerializer` (reported as #3519) which this PR addresses. The deserialize path already preserves the comparator, but `fory.copy(...)` rebuilt `ConcurrentSkipListSet` with the default constructor path, which dropped the comparator and silently changed ordering semantics. This is easy to miss because `Set.equals(...)` still passes even when iteration order changes. ## What does this PR do? Its kind-of two part: fix the bug, and add more test coverage. Specifically: - Preserve the comparator when copying `ConcurrentSkipListSet` by overriding the collection copy construction path in `ConcurrentSkipListSetSerializer`. - Add constructor-matrix coverage for: - `TreeSet` - `TreeMap` - `ConcurrentSkipListSet` - `ConcurrentSkipListMap` - `PriorityQueue` - Exercise both round-trip deserialize and `fory.copy(...)` behavior for the relevant public constructors. - Assert concrete runtime type, comparator nullability, and observable ordering semantics. - Use queue-specific assertions for `PriorityQueue` by comparing drained poll order instead of relying on `equals(...)`. ## Related issues - Fixes #3519 - Related to #3337 - Related to #3343 ## AI Contribution Checklist - [x] Substantial AI assistance was used in this PR: `yes` / `no` - [x] If `yes`, I included a completed [AI Contribution Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs) in this PR description and the required `AI Usage Disclosure`. - [x] If `yes`, I can explain and defend all important changes without AI help. - [x] If `yes`, I reviewed AI-assisted code changes line by line before submission. - [x] If `yes`, I ran adequate human verification and recorded evidence (checks run locally or in CI, pass/fail summary, and confirmation I reviewed results). - [x] If `yes`, I added/updated tests and specs where required. - [x] If `yes`, I validated protocol/performance impacts with evidence when applicable. - [x] If `yes`, I verified licensing and provenance compliance. AI Usage Disclosure - substantial_ai_assistance: yes - scope: code drafting, test design - affected_files_or_subsystems: java/fory-core collection serializers and java/fory-core collection/map serializer tests - human_verification: local verification evidence available: `cd java && ENABLE_FORY_DEBUG_OUTPUT=1 mvn -T16 -pl fory-core -Dtest=org.apache.fory.serializer.collection.CollectionSerializersTest,org.apache.fory.serializer.collection.MapSerializersTest test` -> pass (237 tests, 0 failures) - performance_verification: no dedicated benchmark run; runtime change is limited to the `fory.copy(...)` path for `ConcurrentSkipListSet`, while serialize/deserialize hot paths are unchanged; the added comparator copy matches existing `PriorityQueue` and `ConcurrentSkipListMap` copy behavior - provenance_license_confirmation: repository-local Apache-2.0-compatible changes only; no third-party code introduced ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark No dedicated benchmark was run. This PR is a correctness fix plus test coverage expansion, not a performance optimization: - The runtime code change only affects the `fory.copy(...)` path for `ConcurrentSkipListSet`. - Serialization and deserialization hot paths are unchanged. - The added work is one comparator copy per copied `ConcurrentSkipListSet`, which is expected to be negligible relative to element copy and skip-list insertion work, and is consistent with existing `PriorityQueue` and `ConcurrentSkipListMap` copy behavior. Co-authored-by: Shawn Yang <shawn.ck.yang@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why?
SortedSetSerializerandSortedMapSerializerunconditionally require a(Comparator)constructor for allTreeSet/TreeMapsubclasses. Java constructors are not inherited, so aclass ChildTreeSet extends TreeSet<String>(orclass ChildTreeMap extends TreeMap<String, String>) with only a no-arg constructor throwsUnsupportedOperationExceptionwhen registered with the corresponding serializer.What does this PR do?
Changes both
SortedSetSerializerandSortedMapSerializerto try the(Comparator)constructor first, and fall back to the no-arg constructor if not found. At deserialization time, uses whichever constructor is available. The no-arg fallback uses natural ordering, which is the common case forTreeSet/TreeMapsubclasses.Only throws
UnsupportedOperationExceptionif neither constructor is found.Related issues
Does this PR introduce any user-facing change?
Does this PR introduce any public API change?
No. Existing behavior is preserved for classes that have a
(Comparator)constructor. Classes that previously threwUnsupportedOperationExceptionnow work.Does this PR introduce any binary protocol compatibility change?
No. The write path is unchanged (size + comparator ref), and the read path still reads the same bytes in the same order. Only the post-read instantiation strategy differs (which constructor is used). Wire format is identical in both directions.
Benchmark
Not applicable; the constructor lookup happens once at serializer construction time, not per serialization. The runtime paths (
newCollection/newMap) add a single null check (if (comparatorConstructor != null)) which is negligible.