Skip to content

fix: avoid re-walking already-resolved schemas fragmented by propertyName cache key - #5298

Closed
fgrilli wants to merge 1 commit into
swagger-api:masterfrom
fgrilli:fix/property-name-cache-fragmentation
Closed

fix: avoid re-walking already-resolved schemas fragmented by propertyName cache key#5298
fgrilli wants to merge 1 commit into
swagger-api:masterfrom
fgrilli:fix/property-name-cache-fragmentation

Conversation

@fgrilli

@fgrilli fgrilli commented Aug 25, 2026

Copy link
Copy Markdown

Problem

AnnotatedType.equals()/hashCode() include propertyName whenever schemaProperty is true. Since propertyName is 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 in ModelConverterContextImpl. 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 $ref instead of re-walking it. Scoped to OAS 3.1 specifically because OAS 3.0's $ref can'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 (see Issue5115Test, JsonPropertyTest#testTicket2845, #3366).

Testing

Added JcrLikeDiamondResolutionTimingTest plus 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 $ref short-circuit skips work entirely once a type is defined). Full swagger-core module test suite: 749/749 passing, no regressions.

Note: this fix and #5297 (my fix for #5292) are independent — #5297's guard explicitly excludes the isSubtype case and doesn't require openapi31, 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.

…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
fgrilli marked this pull request as draft August 25, 2026 08:33
@fgrilli fgrilli closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant