8000 Stop mutating shared ParsedPersistenceXmlDescriptor in build steps by Pepo48 · Pull Request #51628 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Stop mutating shared ParsedPersistenceXmlDescriptor in build steps - #51628

Merged
shawkins merged 3 commits into
keycloak:mainfrom
Pepo48:issue-47482
Aug 18, 2026
Merged

shawkins merged 3 commits into
keycloak:mainfrom
Pepo48:issue-47482

Conversation

@Pepo48
@Pepo48 Pepo48 commented Aug 11, 2026
Copy link
Copy Markdown
Contributor

Closes #47482

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Stops mutating the shared ParsedPersistenceXmlDescriptor during Quarkus build steps by switching entity registration to Quarkus’ JPA model contribution mechanism (closes #47482).

Changes:

  • Introduces JpaModelPersistenceUnitContributionBuildItem to contribute entities instead of mutating the parsed persistence unit descriptor.
  • Refactors default PU entity discovery to collect entities and emit a contribution build item.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +637 to +652
List<String> additionalEntities = new ArrayList<>();
for (AnnotationInstance annotation : annotations) {
AnnotationTarget target = annotation.target();
String targetName = target.asClass().name().toString();

if (!userManagedEntities.contains(targetName)
&& (!targetName.startsWith("org.keycloak") || targetName.startsWith("org.keycloak.testsuite"))) {
descriptor.addClasses(targetName);
additionalEntities.add(targetName);
}
}

if (!additionalEntities.isEmpty()) {
jpaModelPuContributions.produce(new JpaModelPersistenceUnitContributionBuildItem(
descriptor.getName(), descriptor.getPersistenceUnitRootUrl(),
additionalEntities, Collections.emptyList()));
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not valid - it doesn't account for the constructor's defensive copy into a TreeSet.

@michalvavrik michalvavrik left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

shawkins
shawkins previously approved these changes Aug 11, 2026
@shawkins shawkins left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@shawkins
shawkins enabled auto-merge (squash) August 11, 2026 16:42
Copilot AI review requested due to automatic review settings August 12, 2026 20:54
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines +649 to +651
jpaModelPuContributions.produce(new JpaModelPersistenceUnitContributionBuildItem(
descriptor.getName(), descriptor.getPersistenceUnitRootUrl(),
additionalEntities, Collections.emptyList()));
@michalvavrik
Copy link
Copy Markdown
Member

@Pepo48 I look at that Base IT (4) and it looks suspicious as there is some DB failure, please investigate.

…lDescriptor

Closes keycloak#47482

Signed-off-by: Peter Zaoral <pzaoral@redhat.com>
Copilot AI review requested due to automatic review settings August 13, 2026 12:19
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +55 to +58
@Override
public List<String> getManagedClassNames() {
return List.copyOf(super.getManagedClassNames());
}
…lDescriptor

Closes keycloak#47482

Signed-off-by: Peter Zaoral <pzaoral@redhat.com>
Copilot AI review requested due to automatic review settings August 13, 2026 13:07
@Pepo48
Pepo48 commented Aug 13, 2026 •
Copy link
Copy Markdown
Contributor Author

@michalvavrik I noticed that yesterday and as you can see based on the timeline here, I tried multiple reruns already, because I could not reproduce it locally.

The CI failure (UnknownEntityTypeException: Company) happened because my initial fix removed descriptor.addClasses() and only produced JpaModelPersistenceUnitContributionBuildItem. That contribution item only registers entities for the build phase, not for runtime. So Hibernate didn't recognize them when the server actually started.

The fix needs both: addClasses() for runtime entity registration and the contribution item for build-time discovery. To prevent the CME while keeping addClasses(), I wrapped the descriptor in a subclass that returns a defensive copy from getManagedClassNames().

That said, this is a workaround. During debugging I realized that it's not Keycloak issue, not even Quarkus one, but the root cause is in Hibernate ORM. ParsedPersistenceXmlDescriptor.getManagedClassNames() returns the raw internal ArrayList instead of a defensive copy. I have a fix (synchronized (Copilot concern is valid) + List.copyOf() on the getters and mutators) that eliminates the CME entirely, verified with a concurrency reproducer test (0 CMEs vs 202/500 rounds on Hibernate without the fix).

Hibernate uses Jira for issue tracking and I don't have access to create issues there, so I'm not sure what the correct process is for contributing without the Jira project access.

The question is then whether we want to proceed with this PR until the Hibernate fix (which would probably need to be backported in order to be able to consume it early).

cc: @shawkins @vmuzikar @ahus1 @pedroigor

@keycloak-github-bot
Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.federation.ldap.LDAPUserLoginTest#loginLDAPUserAuthenticationSimpleEncryptionStartTLS

Keycloak CI - Java Distribution IT (windows-latest - temurin - 21)

org.openqa.selenium.TimeoutException: 
java.net.SocketTimeoutException: Read timed out
Build info: version: '4.28.1', revision: '73f5ad48a2'
System info: os.name: 'Windows Server 2025', os.arch: 'amd64', os.version: '10.0', java.version: '21.0.12'
Driver info: driver.version: HtmlUnitDriver
...

Report flaky test

@keycloak-github-bot keycloak-github-bot Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/ConcurrentSafeParsedPersistenceXmlDescriptor.java:42

  • Please add a focused concurrency regression test that repeatedly calls addClasses while snapshotting getManagedClassNames. This class is the core fix for the reported CME, but currently no test exercises it, despite the repository requiring functional/integration coverage for changes (CONTRIBUTING.md:28).
    public synchronized List<String> getManagedClassNames() {
        return List.copyOf(super.getManagedClassNames());

@shawkins
Copy link
Copy Markdown
Contributor

@Pepo48 just to double check - is there a simple approach to serializing the steps? I know you had a concern about the performance impact - do you have a general idea in terms of milliseconds or microseconds?

@michalvavrik
michalvavrik self-requested a review August 13, 2026 14:18
@michalvavrik
Copy link
Copy Markdown
Member

I see @Pepo48 .

Hibernate uses Jira for issue tracking and I don't have access to create issues there, so I'm not sure what the correct process is for contributing without the Jira project access.

Neither do I, I just followed official links and authenticated under RH account and unauthorized 😀 but we can easily ask Hibernate team or create Quarkus issue. I never bothered by creating Hibernate issues, they just fix Quarkus issue by marking it as blocked external and linking it to Hibernate tracker they fix :-)

That said, this is a workaround. During debugging I realized that it's not Keycloak issue, not even Quarkus one, but the root cause is in Hibernate ORM. ParsedPersistenceXmlDescriptor.getManagedClassNames() returns the raw internal ArrayList instead of a defensive copy. I have a fix (synchronized (Copilot #51628 (comment) is valid) + List.copyOf() on the getters and mutators) that eliminates the CME entirely, verified with a concurrency reproducer test (0 CMEs vs 202/500 rounds on Hibernate without the fix).

If it is Hibernate bug, then it must also be reproducible with Quarkus? I am asking because there is still a part that I don't understand - isn't this issue simply happening because build steps are running concurrently and we are using it in a way that they didn't anticipate? What I am after is that instead of the synchronization fix you provided, I'd love to know how they enforce order in Quarkus that avoids it.

@michalvavrik
Copy link
Copy Markdown
Member

@marko-bekhta gave me this link https://hibernate.zulipchat.com/#narrow/channel/132096-hibernate-user/topic/Jira.20issue.20creation/near/602213094 so it is a known issue and IIUC there is a workaround mentioned in the Zulip.

@vmuzikar
vmuzikar commented Aug 13, 2026 •
Copy link
Copy Markdown
Contributor

I'd report it through Quarkus in any case, we consume Hibernate through them. But since without the fix we have flaky tests, we need to go ahead with the fix on Keycloak side.

Copilot AI review requested due to automatic review settings August 13, 2026 18:46
@Pepo48
Pepo48 commented Aug 13, 2026 •
Copy link
Copy Markdown
Contributor Author

just to double check - is there a simple approach to serializing the steps?

Unless I'm overlooking something obvious, Quarkus doesn't let one extension control the execution order of another extension's build steps. The only way would be a change on the Quarkus side.

isn't this issue simply happening because build steps are running concurrently and we are using it in a way that they didn't anticipate? I'd love to know how they enforce order in Quarkus that avoids it.

Seems like no Quarkus step ever calls addClasses() on PersistenceXmlDescriptorBuildItem, so that's how. If I understand their approach right, Quarkus expects build items to be immutable after production - produce once, then only read. We broke this convention by modifying the descriptor in a consumer step while another consumer reads it concurrently.

So I tried one more thing, instead of workarounds, I tried to follow Quarkus convention - I moved the modification into the producer step so the descriptor is fully built before anyone can read it.

One caveat is that the original code excluded entities that already belong to a user-defined persistence unit. That check depended on reading the full list of persistence units, which isn't available in the step that creates one. To keep the check, the producer step reads the user-defined persistence XML files directly. Again, from what I see this goes against how Quarkus normally handles it, but I haven't found any other option that would avoid bringing back the race.

Please, take a look and let me know, which one of the approaches seems to be better.

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java:557

  • This no longer excludes entities explicitly assigned to a user-defined persistence unit, so every non-Keycloak @Entity is also added to the default unit. For example, com.acme.provider.legacy.jpa.entity.Realm is listed in new-user-store, and CustomJpaEntityProviderDistTest.testUserManagedEntityNotAddedToDefaultPU requires it to appear only once; retain the user-managed exclusion while avoiding descriptor mutation, e.g. by producing a separate default-unit JPA model contribution after collecting those class names.
                if (!targetName.startsWith("org.keycloak") || targetName.startsWith("org.keycloak.testsuite")) {
                    additionalEntities.add(targetName);

…lDescriptor

Closes keycloak#47482

Signed-off-by: Peter Zaoral <pzaoral@redhat.com>
Copilot AI review requested due to automatic review settings August 13, 2026 20:21
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@michalvavrik michalvavrik left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good approach.

@ahus1
ahus1 removed their request for review August 17, 2026 10:08
@ahus1
ahus1 commented Aug 17, 2026
Copy link
Copy Markdown
Member

@Pepo48 - thanks for looping me into this one, but as this is diving deeply into Quarkus and how you plan to consume fixes and releases, I'm afraid I can't help much here.

I'm still interested in fix, as I'm affected by the unstable test, and - to my understanding - this might also happen when people use the Keycloak distribution. With that, I've marked this one as a blocker for the upcoming release.

@shawkins shawkins left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @Pepo48.

So at worst case there will be a little more build time processing for those using additional persistence xml files.

@shawkins
shawkins merged commit d00d1c5 into keycloak:main Aug 18, 2026
225 of 231 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quarkus-next] ConcurrentModificationException in parallel Quarkus build steps

6 participants

0