fix: avoid re-walking already-resolved schemas fragmented by propertyName cache key - #5298
Closed
fgrilli wants to merge 1 commit into
Closed
fix: avoid re-walking already-resolved schemas fragmented by propertyName cache key#5298fgrilli wants to merge 1 commit into
fgrilli wants to merge 1 commit into
Conversation
…cache key AnnotatedType.equals()/hashCode() include propertyName whenever schemaProperty is true, so the same target class reached via many differently-named getters (common with third-party APIs like the JCR interfaces) occupies a distinct cache entry per getter name. Each entry re-triggers a full resolveSchemaMembers() walk, so resolution time grows combinatorially with depth even though the number of distinct classes involved stays small. Short-circuit to a $ref when, in OAS 3.1, a schema-property resolution (not a subtype, no per-property annotations/JsonView) targets a class that's already been fully resolved into a named component schema - reusing the existing definition instead of re-walking it. Adds JcrLikeDiamondResolutionTimingTest plus a generated fixture (gen_jcr_diamond.py) reproducing the shape of the JCR API's fan-out.
fgrilli
marked this pull request as draft
August 25, 2026 08:33
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.
Problem
AnnotatedType.equals()/hashCode()includepropertyNamewheneverschemaPropertyis true. SincepropertyNameis the name of the declaring getter, the exact same target class reached via two differently-named getters (even from different parent classes) is treated as two distinct cache entries inModelConverterContextImpl. Each distinct entry re-triggers a full re-walk of that target class's own properties, which recurses the same way into whatever that class references. For a densely interconnected API — the same handful of classes reachable via many differently-named getters, several levels deep (exactly the shape of a third-party API an application doesn't control, e.g. the JCR interfaces:Node,NodeType,NodeTypeManager,Session,Workspace, ...) — this makes the number of effective resolutions grow combinatorially with depth, even though the number of classes involved is small.This is related to, but structurally distinct from, #5091 (fixed by #5114): that issue/fix was scoped to the
@ArraySchema(schema = @Schema(implementation = ...))annotation-driven path. This one is in the plain Jackson bean-introspection path — no annotations involved at all.Fix
In
ModelResolver, when resolving a schema-property (not a subtype, no per-property annotations/JsonView) in OAS 3.1 mode, if the target class has already been fully resolved into a named component schema, short-circuit to a$refinstead of re-walking it. Scoped to OAS 3.1 specifically because OAS 3.0's$refcan't carry sibling keywords — some per-property overrides there are instead baked directly into the shared named schema itself, which genuinely needs the full walk to reproduce (seeIssue5115Test,JsonPropertyTest#testTicket2845, #3366).Testing
Added
JcrLikeDiamondResolutionTimingTestplus a generator script (gen_jcr_diamond.py) that produces two structurally-identical diamond DAGs mimicking the JCR API's fan-out shape (fan-out F=60, 3 hops): one with unique getter names per edge into a shared target (the bug-triggering shape), one with shared/canonical names (control). The test asserts resolving the "unique" variant isn't disproportionately slower than the "shared" variant.Before the fix, the unique-name variant took several times longer than the shared-name control at this scale; after the fix it's actually faster (241ms vs 489ms baseline in one run — the
$refshort-circuit skips work entirely once a type is defined). Fullswagger-coremodule test suite: 749/749 passing, no regressions.Note: this fix and #5297 (my fix for #5292) are independent — #5297's guard explicitly excludes the
isSubtypecase and doesn't requireopenapi31, so the two don't overlap, and #5297's OOM repro (ModelConverters.getInstance().readAll(javax.jcr.Node.class)in OAS 3.0 mode) is unaffected by this PR.