Describe the bug
ConsistentHash is annotated @ThreadSafe, and most of its accessors return defensive copies — e.g. getNodes() returns new HashSet<>(nodes.keySet()). getPartition() is the exception: it hands back the internal set directly.
public Set<Partition<T>> getPartition(T node) {
lock.readLock().lock();
try {
return nodes.get(node); // internal mutable state, not a copy
} finally {
lock.readLock().unlock();
}
}
The returned set is the same instance stored in the internal nodes map, so a caller can mutate the ring's state through it (e.g. getPartition(node).clear()), and the reference escapes after the read lock is released. This is inconsistent with getNodes() and weakens the @ThreadSafe contract.
It's latent today — getPartition() has no production caller (only tests), and the internal set isn't mutated after a node is added — but it's a straightforward encapsulation fix worth making before anything starts to depend on it.
Expected behavior
getPartition() returns a defensive copy, like getNodes(), so callers can't reach the ring's internal state.
Describe the bug
ConsistentHashis annotated@ThreadSafe, and most of its accessors return defensive copies — e.g.getNodes()returnsnew HashSet<>(nodes.keySet()).getPartition()is the exception: it hands back the internal set directly.The returned set is the same instance stored in the internal
nodesmap, so a caller can mutate the ring's state through it (e.g.getPartition(node).clear()), and the reference escapes after the read lock is released. This is inconsistent withgetNodes()and weakens the@ThreadSafecontract.It's latent today —
getPartition()has no production caller (only tests), and the internal set isn't mutated after a node is added — but it's a straightforward encapsulation fix worth making before anything starts to depend on it.Expected behavior
getPartition()returns a defensive copy, likegetNodes(), so callers can't reach the ring's internal state.