fix(apt): remove incorrect 'new QClass()' suggestion from circular Q-class warning - #1905
fix(apt): remove incorrect 'new QClass()' suggestion from circular Q-class warning#1905o54711254 wants to merge 7 commits into
Conversation
…class warning
Suggestion (3) in the circular Q-class warning claimed that using
'new QClass("alias")' would avoid the initialization deadlock, but
instance creation triggers class initialization (JLS 12.4.1) just like
static field access does, so the same deadlock occurs.
Follow-up to OpenFeign#1739.
Code quality reviewThe diagnosis is right — But I think deletion is the wrong move here, and the surrounding code has defects that undercut the PR's own goal. 1. This deletes a mitigation the processor already knows how to enable
So suggestion (3) wasn't wrong, it was half-written. Suggest restoring (3) in its complete form: 2. The warning also prints incorrect cycles
Neither test covers this: The fix is one line: slice the path from the neighbour's position instead of copying it whole. If the point of this PR is "the warning tells users something untrue", the untrue cycle list belongs in scope. 3. Detection is enabled by HashMap iteration ordervar serializerConfig = conf.getSerializerConfig(context.entityTypes.values().iterator().next());
if (serializerConfig.createDefaultVariable()) {
The deadlock is a property of individual types, so the config check belongs inside the graph walk rather than being sampled once globally: only types whose own config has 4. Structural: this feature doesn't belong in this file
Extracting a While in there: the six Suggested path: fold 1–3 into this PR (they're all "the warning is wrong", same theme, ~15 lines), and do the extraction in 4 first so 2 and 3 can actually be tested. |
Move the entity-reference graph walk out of AbstractQuerydslProcessor into a standalone QClassCycleDetector. The processor now only formats the message and calls Messager.printMessage. The detector holds path/inStack/globalVisited/cycles as fields, so the recursive walk shrinks from a six-parameter dfs() to visit(EntityType). Behavior is unchanged; this enables unit tests on plain EntityType fixtures instead of full javac compile tests.
Covers the base cases (no cycles, unidirectional, self-reference, unknown type, two-node cycle, three-node cycle) directly against EntityType fixtures. Tests for cycle-reporting edge cases follow in subsequent commits alongside the fixes they exercise.
visit() previously copied the entire traversal path into the detected cycle, so A→B→C→B was reported as A→B→C→B even though A is not part of the loop. Slice the path from the neighbor's own position instead, so only the cycle proper is emitted. Path is now a List<EntityType> keyed on full names, which also disambiguates entities that share a simple name.
The previous gate picked one arbitrary entity via HashMap.values().iterator().next() and checked its serializer config; whether the detector ran at all depended on hash order. Per-type or per-package @config users got warnings that appeared or disappeared between runs. Filter entityTypes per entity by createDefaultVariable instead, since only entities that generate a static default variable can participate in a class-init deadlock chain. Sort the detector's start iteration by full name so cycles are reported in a stable order regardless of the caller's map ordering.
Suggestion (3) was removed earlier as misleading, but it is
valid when paired with -Aquerydsl.createDefaultVariable=false:
disabling the generated static default makes 'new QClass("alias")'
the only entry point and removes the <clinit> chain that causes
deadlock in the first place.
Reinstate it as a distinct option so users who cannot restructure
their entity graph still have a workable mitigation.
The seven StringBuilder.append calls make the message hard to read and the wrapping pattern was inconsistent. Collapse the fixed portion into a single text block and interpolate the generated cycle list through %s. Output is byte-for-byte identical.
|
Thanks for the detailed review — addressed all four points: (1) Restored suggestion (3) with the required flag — added back verbatim as you suggested. (2) Cycle vs entry path — sliceCycleFrom now slices from the back-edge target using path.indexOf(entry) instead of copying the whole DFS path. The path now stores EntityType instances, while traversal identity uses full names, so entities sharing a simple name aren't conflated. (3) Nondeterministic detection — removed the sample-one-entity gate. Each entity is now filtered individually by its own createDefaultVariable config before the walk, and the detector iterates starts sorted by full name so cycle report order is stable regardless of map ordering. Added a unit test that shuffles the input map and asserts stable output. (4) Extract detector — pulled ~65 lines into QClassCycleDetector with 8 unit tests on plain fixtures. The message.append( StringBuilder chain is now a single text block with %s interpolation for the cycle list — output is byte-for-byte identical. |
Background
In #1739 I added compile-time detection for circular Q-class references that can
cause class initialization deadlocks. The warning message suggests three
workarounds:
Suggestion (3) does not work. This PR removes it.
Why (3) does not work
Creating an instance of a class triggers class initialization (JLS 12.4.1),
exactly as accessing a static field does. So
new QOrder("alias")still runsQOrder.<clinit>, which initializes the static field, whose constructorcreates the other Q-class — the same chain that causes the deadlock.
Given a bidirectional
@OneToOnebetweenOrderandPayment, the generatedclass looks like this:
new QOrder("alias")→ triggers
QOrder.<clinit>→ initializes the static field
order→ its constructor creates
QPayment→ triggers
QPayment.<clinit>Avoiding static field access does not avoid static field initialization.
Reproduction
Two threads, each entering only via
new:Output:
Neither thread completed — both were blocked on class initialization monitors.
Change
Removes suggestion (3) from the warning message. (1) and (2) remain valid and
are unaffected. The detection logic itself is unchanged.
Follow-up to #1739. Sorry for the incorrect guidance in the original PR.