-
Notifications
You must be signed in to change notification settings - Fork 0
Automated Test: feature-group-concurrency-implementation #314
Automated Test: feature-group-concurrency-implementation #314
Conversation
Closes #40368 Signed-off-by: vramik <vramik@redhat.com>
📝 WalkthroughWalkthroughThis pull request refactors group caching and retrieval logic to improve null-safety, adds clarity to method overrides, removes an unused helper method, and introduces concurrency testing for group operations. The changes span cache layer optimization, utility cleanup, and concurrent access validation. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java (1)
253-269:⚠️ Potential issue | 🟠 MajorInconsistent null-safety: similar methods lack null checks.
The
getSubGroupsCount()method now includes a null-safe check onmodelSupplier.get(), but the adjacent overloadedgetSubGroupsStream()methods (lines 256, 262, 268) callmodelSupplier.get().getSubGroupsStream(...)directly without null checks. If concurrent deletion can cause a null model in one case, it can in all.Consider applying the same defensive pattern to these methods for consistency:
🛡️ Proposed fix for consistent null-safety
`@Override` public Stream<GroupModel> getSubGroupsStream(String search, Integer firstResult, Integer maxResults) { if (isUpdated()) return updated.getSubGroupsStream(search, firstResult, maxResults); - return modelSupplier.get().getSubGroupsStream(search, firstResult, maxResults); + GroupModel model = modelSupplier.get(); + return model == null ? Stream.empty() : model.getSubGroupsStream(search, firstResult, maxResults); } `@Override` public Stream<GroupModel> getSubGroupsStream(Integer firstResult, Integer maxResults) { if (isUpdated()) return updated.getSubGroupsStream(firstResult, maxResults); - return modelSupplier.get().getSubGroupsStream(firstResult, maxResults); + GroupModel model = modelSupplier.get(); + return model == null ? Stream.empty() : model.getSubGroupsStream(firstResult, maxResults); } `@Override` public Stream<GroupModel> getSubGroupsStream(String search, Boolean exact, Integer firstResult, Integer maxResults) { if (isUpdated()) return updated.getSubGroupsStream(search, exact, firstResult, maxResults); - return modelSupplier.get().getSubGroupsStream(search, exact, firstResult, maxResults); + GroupModel model = modelSupplier.get(); + return model == null ? Stream.empty() : model.getSubGroupsStream(search, exact, firstResult, maxResults); }
🤖 Fix all issues with AI agents
In `@tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java`:
- Line 121: Rename the misspelled variable groupUuuids to groupUuids in the
GroupTest class: update the declaration (List<String> groupUuuids = new
ArrayList<>();) and every usage of groupUuuids (including the two later
references mentioned) to the corrected identifier groupUuids so the code
compiles and reads correctly.
- Around line 118-158: The reader thread in createMultiDeleteMultiReadMulti is
never joined, so the test can finish before it completes; change the anonymous
Thread to a named Thread (e.g., reader), start it, perform deletions as-is, set
deletedAll.set(true), then call reader.join() and handle InterruptedException
(fail the test or re-interrupt) so any exceptions added to caughtExceptions
after deletions are observed before assertThat runs; keep the existing
caughtExceptions and deletedAll variables and the
managedRealm.admin().groups().groups(...) read loop unchanged.
🧹 Nitpick comments (1)
tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java (1)
144-147: Empty catch block swallows exception details during debugging.The blank line in the catch block (line 145) appears unintentional. While exceptions are captured in
caughtExceptions, consider logging them for easier debugging if the test fails.📝 Proposed improvement
} catch (Exception e) { - caughtExceptions.add(e); + caughtExceptions.add(e); + // Optionally log for debugging: System.err.println("Read exception: " + e.getMessage()); }
| @Test | ||
| public void createMultiDeleteMultiReadMulti() { | ||
| // create multiple groups | ||
| List<String> groupUuuids = new ArrayList<>(); | ||
| IntStream.range(0, 100).forEach(groupIndex -> { | ||
| GroupRepresentation group = new GroupRepresentation(); | ||
| group.setName("Test Group " + groupIndex); | ||
| try (Response response = managedRealm.admin().groups().add(group)) { | ||
| boolean created = response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL; | ||
| if (created) { | ||
| final String groupUuid = ApiUtil.getCreatedId(response); | ||
| groupUuuids.add(groupUuid); | ||
| } else { | ||
| fail("Failed to create group: " + response.getStatusInfo().getReasonPhrase()); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| AtomicBoolean deletedAll = new AtomicBoolean(false); | ||
| List<Exception> caughtExceptions = new CopyOnWriteArrayList<>(); | ||
| // read groups in a separate thread | ||
| 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); | ||
|
|
||
| assertThat(caughtExceptions, Matchers.empty()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread is not joined: test may complete before reader thread finishes.
The reader thread (lines 139-149) is started but never joined. The test assertion at line 157 may execute before the thread has finished its final iteration, potentially missing exceptions. Additionally, if an exception occurs in the final read after deletedAll is set to true, it won't be captured.
🧵 Proposed fix to properly synchronize thread completion
AtomicBoolean deletedAll = new AtomicBoolean(false);
List<Exception> caughtExceptions = new CopyOnWriteArrayList<>();
// read groups in a separate thread
- new Thread(() -> {
+ Thread readerThread = 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();
+ });
+ readerThread.start();
// delete groups
- groupUuuids.forEach(groupUuid -> {
+ groupUuuids.forEach(groupUuid -> {
managedRealm.admin().groups().group(groupUuid).remove();
});
deletedAll.set(true);
+ // Wait for reader thread to complete
+ try {
+ readerThread.join(5000); // 5 second timeout
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ fail("Reader thread interrupted");
+ }
+
assertThat(caughtExceptions, Matchers.empty());🤖 Prompt for AI Agents
In `@tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java`
around lines 118 - 158, The reader thread in createMultiDeleteMultiReadMulti is
never joined, so the test can finish before it completes; change the anonymous
Thread to a named Thread (e.g., reader), start it, perform deletions as-is, set
deletedAll.set(true), then call reader.join() and handle InterruptedException
(fail the test or re-interrupt) so any exceptions added to caughtExceptions
after deletions are observed before assertThat runs; keep the existing
caughtExceptions and deletedAll variables and the
managedRealm.admin().groups().groups(...) read loop unchanged.
| @Test | ||
| public void createMultiDeleteMultiReadMulti() { | ||
| // create multiple groups | ||
| List<String> groupUuuids = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in variable name: groupUuuids should be groupUuids.
The variable has an extra 'u'.
✏️ Proposed fix
- List<String> groupUuuids = new ArrayList<>();
+ List<String> groupUuids = new ArrayList<>();Also update references at lines 129 and 152.
🤖 Prompt for AI Agents
In `@tests/base/src/test/java/org/keycloak/tests/admin/group/GroupTest.java` at
line 121, Rename the misspelled variable groupUuuids to groupUuids in the
GroupTest class: update the declaration (List<String> groupUuuids = new
ArrayList<>();) and every usage of groupUuuids (including the two later
references mentioned) to the corrected identifier groupUuids so the code
compiles and reads correctly.
This pull request was automatically created by
@coderabbitai/e2e-reviewer.Batch created pull request.
Summary by CodeRabbit
Bug Fixes
Tests