KAFKA-20846:Make the streams assignor GroupSpec deeply unmodifiable - #22970
KAFKA-20846:Make the streams assignor GroupSpec deeply unmodifiable#22970gabriellefu wants to merge 4 commits into
Conversation
| void testTaskSetsAreDeeplyUnmodifiable() { | ||
| MemberMetadataAndStateImpl member = memberWith( | ||
| Map.of("subtopology-1", new HashSet<>(Set.of(0, 1))), | ||
| Map.of() |
There was a problem hiding this comment.
Using Map.of gives us already an unmodifiable Map, so the test would pass even w/o the fix. We need to ensure that the outer Map is writable here, to verify that we indeed wrap it with an unmodifiable view.
There was a problem hiding this comment.
got it, changed
| void testTaskOffsetsAreDeeplyUnmodifiable() { | ||
| MemberMetadataAndStateImpl member = memberWith( | ||
| Map.of(), | ||
| Map.of("subtopology-1", new HashMap<>(Map.of(0, 100L))) |
|
|
||
| assertThrows(UnsupportedOperationException.class, () -> member.taskOffsets().put("subtopology-2", Map.of())); | ||
| assertThrows(UnsupportedOperationException.class, () -> member.taskOffsets().get("subtopology-1").put(0, 200L)); | ||
| assertThrows(UnsupportedOperationException.class, () -> member.taskEndOffsets().get("subtopology-1").put(1, 200L)); |
There was a problem hiding this comment.
added another put test
gabriellefu
left a comment
There was a problem hiding this comment.
updated base on @mjsax 's review
|
|
||
| assertThrows(UnsupportedOperationException.class, () -> member.taskOffsets().put("subtopology-2", Map.of())); | ||
| assertThrows(UnsupportedOperationException.class, () -> member.taskOffsets().get("subtopology-1").put(0, 200L)); | ||
| assertThrows(UnsupportedOperationException.class, () -> member.taskEndOffsets().get("subtopology-1").put(1, 200L)); |
There was a problem hiding this comment.
added another put test
mjsax
left a comment
There was a problem hiding this comment.
Let's make deep copies instead of just views for the inner collections to keep it symmetric -- we also deep-copy the outer collections.
|
|
||
| private static Map<String, Set<Integer>> unmodifiableTasks(Map<String, Set<Integer>> tasks) { | ||
| return Objects.requireNonNull(tasks).entrySet().stream() | ||
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Collections.unmodifiableSet(entry.getValue()))); |
There was a problem hiding this comment.
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Collections.unmodifiableSet(entry.getValue()))); | |
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Set.copyOf(entry.getValue()))); |
|
|
||
| private static Map<String, Map<Integer, Long>> unmodifiableOffsets(Map<String, Map<Integer, Long>> offsets) { | ||
| return Objects.requireNonNull(offsets).entrySet().stream() | ||
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Collections.unmodifiableMap(entry.getValue()))); |
There was a problem hiding this comment.
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Collections.unmodifiableMap(entry.getValue()))); | |
| .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> Map.copyOf(entry.getValue()))); |
| } | ||
|
|
||
| @Test | ||
| public void testCreateMemberMetadataAndStateReturnsUnmodifiableCollections() { |
There was a problem hiding this comment.
I think we can actually remove this test? It seems to replicate what we already get from MemberMetadataAndStateImplTest so no need to duplicate test coverage. We reach the same MemberMetadataAndStateImpl code just from our more layer around it -- doesn't really add anything.
We should just move the client-tag test coverage that we only have here, to the other test.
Changes
MemberMetadataAndStateImplnow wraps the nested collections ofactiveTasks,standbyTasks,warmupTasks,taskOffsets, andtaskEndOffsets.GroupSpecImplnow wraps bothmembersandconfigs, and thenow-redundant wrapping in
TargetAssignmentBuilderis removed soimmutability is enforced by the type rather than at each call site.
MockAssignornow deep-copies the active task sets it grows. Itpreviously relied on input sets being mutable (
new HashMap<>(...)followed by
computeIfAbsent(...).add(...)), which now throws.GroupSpec#configs,MemberAssignmentMetadata#clientTags, andMemberAssignmentState.Testing
TargetAssignmentBuilderTest#testCreateMemberMetadataAndStateReturnsUnmodifiableCollectionsbuilds a member and its reported offsets from mutable collections,
asserts that every map and nested collection exposed to the assignor
rejects mutation, and verifies that the member’s own tasks remain
unchanged.
GroupSpecImplTest#testMembersAndConfigsAreUnmodifiablecovers bothmembers()andconfigs().this change.
MockAssignorTestandStickyTaskAssignorTestpass unchanged,covering the two in-tree assignors against the tightened collections.
Reviewers: Matthias J. Sax matthias@confluent.io