Skip to content

Fix concurrent group access to prevent NullPointerException#1

Open
ShashankFC wants to merge 1 commit into
feature-group-concurrency-updatefrom
feature-group-concurrency-implementation
Open

Fix concurrent group access to prevent NullPointerException#1
ShashankFC wants to merge 1 commit into
feature-group-concurrency-updatefrom
feature-group-concurrency-implementation

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Feb 13, 2026

Copy link
Copy Markdown

User description

Test 10

Summary by CodeRabbit

Release Notes

  • Tests

    • Added comprehensive concurrent group operations test to validate reliability during simultaneous create, read, and delete operations.
  • Bug Fixes

    • Improved group caching stability with enhanced null-safety checks.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#10


CodeAnt-AI Description

Prevent NullPointerException when groups are accessed concurrently and add a concurrency test

What Changed

  • getSubGroupsCount now safely handles a missing underlying group model and returns null instead of throwing a NullPointerException when the cached supplier yields no model.
  • Marked cached group realm accessor as an override to align behavior with the cached entity.
  • Removed an unused group search helper method.
  • Added a new integration test that concurrently creates 100 groups, reads the group list in a background thread while deleting the groups, and asserts no exceptions occur during concurrent read/delete operations.

Impact

✅ Fewer NullPointerExceptions when groups are accessed concurrently
✅ Safer group deletion while other requests read group lists
✅ Earlier detection of concurrency regressions through a targeted integration test

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Closes #40368

Signed-off-by: vramik <vramik@redhat.com>
@ShashankFC

Copy link
Copy Markdown
Author

@CodeAnt-AI: review

@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

CodeAnt AI is running the review.

@codeant-ai codeant-ai Bot added the size:M This PR changes 30-99 lines, ignoring generated files label Feb 13, 2026
@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

Sequence Diagram

The PR fixes a null-safety bug in cached group access and adds a concurrent test that creates many groups, reads them repeatedly in a background thread while the main thread deletes them, and asserts no exceptions (NPEs) occur. The diagram shows the high-level concurrent create/read/delete flow exercised by the test and the cache access point that was hardened.

sequenceDiagram
    participant TestRunner
    participant AdminAPI
    participant GroupCache
    participant BackendStore

    TestRunner->>AdminAPI: Create many groups (loop)
    AdminAPI->>BackendStore: Persist groups
    TestRunner->>AdminAPI: Start background reader thread
    loop while deletions pending
        AdminAPI->>GroupCache: List groups (concurrent read)
        GroupCache->>BackendStore: Fetch group data (modelSupplier.get())
        BackendStore-->>GroupCache: Group data (may be null during concurrent delete)
        GroupCache-->>AdminAPI: Return group list (null-safe)
    end
    TestRunner->>AdminAPI: Delete groups (main thread)
    AdminAPI->>BackendStore: Remove groups
    TestRunner-->>TestRunner: Assert no exceptions collected (no NPEs)
Loading

Generated by CodeAnt AI

@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Inconsistent null handling
    getRoleMappings(...) now guards against a null group supplier and returns an empty set, but getSubGroups(...) does not perform the same null check. This inconsistency can lead to different behavior or NPEs depending on which lazy loader is used. Consider applying the same null-safety pattern to getSubGroups(...).

  • Possible Null Return
    The updated implementation of getSubGroupsCount() returns null when the modelSupplier yields null. Callers that auto-unbox the returned Long or expect a non-null value may encounter a NullPointerException. Revisit the intended semantics (null vs 0) and make the behavior consistent with other methods.

  • Test thread lifecycle
    The new test createMultiDeleteMultiReadMulti() spawns a reader thread but does not join or otherwise ensure it terminates before the test method returns. This can cause flaky behavior, resource leaks, or uncaught exceptions to be missed. Ensure the thread is properly managed and any exceptions are propagated to the test.

Comment on lines +139 to +155
new Thread(() -> {
while (!deletedAll.get()) {
try {
// just loading briefs
managedRealm.admin().groups().groups(null, 0, Integer.MAX_VALUE, true);
} catch (Exception e) {

caughtExceptions.add(e);
}
}
}).start();

// delete groups
groupUuuids.forEach(groupUuid -> {
managedRealm.admin().groups().group(groupUuid).remove();
});
deletedAll.set(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The concurrency test starts a background thread to read groups but never joins it, so the test can complete (and assert that no exceptions occurred) before the reader thread actually runs or finishes, making the test flaky and potentially missing concurrent-access failures it is supposed to detect. [logic error]

Severity Level: Major ⚠️
- ⚠️ Concurrency regression test may miss group cache failures.
- ⚠️ CI may report green despite existing concurrent group bug.
Suggested change
new Thread(() -> {
while (!deletedAll.get()) {
try {
// just loading briefs
managedRealm.admin().groups().groups(null, 0, Integer.MAX_VALUE, true);
} catch (Exception e) {
caughtExceptions.add(e);
}
}
}).start();
// delete groups
groupUuuids.forEach(groupUuid -> {
managedRealm.admin().groups().group(groupUuid).remove();
});
deletedAll.set(true);
Thread reader = new Thread(() -> {
while (!deletedAll.get()) {
try {
// just loading briefs
managedRealm.admin().groups().groups(null, 0, Integer.MAX_VALUE, true);
} catch (Exception e) {
caughtExceptions.add(e);
}
}
});
reader.start();
// delete groups
groupUuuids.forEach(groupUuid -> {
managedRealm.admin().groups().group(groupUuid).remove();
});
deletedAll.set(true);
try {
reader.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fail("Reader thread was interrupted");
}
Steps of Reproduction ✅
1. Run the JUnit test method `createMultiDeleteMultiReadMulti()` in
`tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java:119-158` (e.g.,
via `mvn test
-Dtest=org.keycloak.tests.admin.group.GroupTest#createMultiDeleteMultiReadMulti`).

2. The test creates and deletes many groups on the main thread at `GroupTest.java:151-154`
while starting a background reader thread at `GroupTest.java:139-149` that repeatedly
calls `managedRealm.admin().groups().groups(...)`.

3. The main thread sets `deletedAll` to `true` at `GroupTest.java:155` and immediately
asserts `caughtExceptions` is empty at `GroupTest.java:157` without joining the reader
thread, so the assertion can run before the reader thread has executed or finished its
loop.

4. If the reader thread encounters a concurrent-access bug and catches an exception later
in its loop at `GroupTest.java:140-147`, it adds it to `caughtExceptions` after the
assertion has already passed, causing the test to report success even though concurrent
group reads are failing.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java
**Line:** 139:155
**Comment:**
	*Logic Error: The concurrency test starts a background thread to read groups but never joins it, so the test can complete (and assert that no exceptions occurred) before the reader thread actually runs or finishes, making the test flaky and potentially missing concurrent-access failures it is supposed to detect.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@codeant-ai

codeant-ai Bot commented Feb 13, 2026

Copy link
Copy Markdown

CodeAnt AI finished running the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants