java-topology/defects/solr/unit/ActiveReplicaWatcherContains.java

234 lines
8.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* CWE-407 unit test: solr-002
* ActiveReplicaWatcher.java:161,169 — replicaIds.contains() and solrCoreNames.contains()
* (ArrayList) inside nested shard × replica loop — O(S × R × N) per ZK event.
*
* Slow path: ArrayList.contains() — O(N) per replica check.
* Fast path: HashSet.contains() — O(1) per replica check.
*
* Compile: javac -d . ActiveReplicaWatcherContains.java
* Run: java -ea unit.ActiveReplicaWatcherContains
*/
public class ActiveReplicaWatcherContains {
/** Simulates a Replica: has an ID and a core name. */
static class Replica {
String replicaId;
String coreName;
boolean active;
Replica(String replicaId, String coreName, boolean active) {
this.replicaId = replicaId;
this.coreName = coreName;
this.active = active;
}
}
/** Simulates a Slice (shard). */
static class Slice {
List<Replica> replicas;
Slice(List<Replica> replicas) { this.replicas = replicas; }
}
/**
* Simulates the defective onStateChanged loop (ArrayList-based).
* Returns total comparison count across both contains() calls.
*/
static long slowOnStateChanged(List<String> watchedReplicaIds,
List<String> watchedCoreNames,
List<Slice> slices) {
List<String> mutableReplicaIds = new ArrayList<>(watchedReplicaIds);
List<String> mutableCoreNames = new ArrayList<>(watchedCoreNames);
List<Replica> activeReplicas = new ArrayList<>();
long comparisons = 0;
for (Slice slice : slices) { // O(S)
for (Replica replica : slice.replicas) { // O(R)
// First contains() — O(replicaIds.size())
comparisons += mutableReplicaIds.size();
if (mutableReplicaIds.contains(replica.replicaId)) {
if (replica.active) {
activeReplicas.add(replica);
mutableReplicaIds.remove(replica.replicaId);
}
} else {
// Second contains() — O(coreNames.size())
comparisons += mutableCoreNames.size();
if (mutableCoreNames.contains(replica.coreName)) {
if (replica.active) {
activeReplicas.add(replica);
mutableCoreNames.remove(replica.coreName);
}
}
}
}
}
return comparisons;
}
/**
* Simulates the fixed onStateChanged loop using HashSet.
* Returns total hash operations.
*/
static long fastOnStateChanged(List<String> watchedReplicaIds,
List<String> watchedCoreNames,
List<Slice> slices) {
Set<String> mutableReplicaIds = new HashSet<>(watchedReplicaIds);
Set<String> mutableCoreNames = new HashSet<>(watchedCoreNames);
List<Replica> activeReplicas = new ArrayList<>();
long operations = 0;
for (Slice slice : slices) {
for (Replica replica : slice.replicas) {
operations++; // O(1) hash lookup for replicaId
if (mutableReplicaIds.contains(replica.replicaId)) {
if (replica.active) {
activeReplicas.add(replica);
mutableReplicaIds.remove(replica.replicaId); // O(1)
}
} else {
operations++; // O(1) hash lookup for coreName
if (mutableCoreNames.contains(replica.coreName)) {
if (replica.active) {
activeReplicas.add(replica);
mutableCoreNames.remove(replica.coreName); // O(1)
}
}
}
}
}
return operations;
}
/** Build slices with replicas. */
static List<Slice> buildSlices(int S, int R) {
List<Slice> slices = new ArrayList<>();
for (int s = 0; s < S; s++) {
List<Replica> replicas = new ArrayList<>();
for (int r = 0; r < R; r++) {
String id = "replica_" + s + "_" + r;
String core = "core_" + s + "_" + r;
replicas.add(new Replica(id, core, (r == 0))); // first replica active
}
slices.add(new Slice(replicas));
}
return slices;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness — watched replicas are found
{
total++;
List<String> watchedIds = new ArrayList<>();
watchedIds.add("replica_0_0");
watchedIds.add("replica_1_0");
List<String> watchedCores = new ArrayList<>();
watchedCores.add("core_2_1");
List<Slice> slices = buildSlices(3, 2);
// Mark replica_2_1 as active for core-name path
slices.get(2).replicas.get(1).active = true;
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices);
assert slowCost > 0;
System.out.printf("Test 1 (correctness): slow did %d comparisons%n", slowCost);
passed++;
}
// Test 2: both find same replicas
{
total++;
List<String> watchedIds = new ArrayList<>();
watchedIds.add("replica_0_0");
List<String> watchedCores = new ArrayList<>();
watchedCores.add("core_1_0");
List<Slice> slices1 = buildSlices(5, 3);
List<Slice> slices2 = buildSlices(5, 3);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
// Both should find the same active replicas — verifiable by same completion behavior
assert slowCost > 0 && fastCost > 0;
System.out.printf("Test 2 (both paths): slow=%d fast=%d%n", slowCost, fastCost);
passed++;
}
// Test 3: cost comparison — medium cluster
{
total++;
int S = 100; // shards
int R = 3; // replicas/shard
int W = 50; // watched replica IDs
List<String> watchedIds = new ArrayList<>();
for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0");
List<String> watchedCores = new ArrayList<>();
for (int i = 0; i < W; i++) watchedCores.add("core_" + (i + W) + "_0");
List<Slice> slices1 = buildSlices(S, R);
List<Slice> slices2 = buildSlices(S, R);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
assert slowCost > fastCost * 3 :
String.format("Expected slow >> fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (S=%d R=%d W=%d): slow=%d, fast=%d, ratio=%.1fx%n",
S, R, W, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: large cluster — significant speedup
{
total++;
int S = 500;
int R = 3;
int W = 200;
List<String> watchedIds = new ArrayList<>();
for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0");
List<String> watchedCores = new ArrayList<>();
for (int i = 0; i < W; i++) watchedCores.add("core_" + i + "_0");
List<Slice> slices1 = buildSlices(S, R);
List<Slice> slices2 = buildSlices(S, R);
List<String> wi2 = new ArrayList<>(watchedIds);
List<String> wc2 = new ArrayList<>(watchedCores);
long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1);
long fastCost = fastOnStateChanged(wi2, wc2, slices2);
double ratio = (double) slowCost / fastCost;
assert ratio > 10.0 :
String.format("Expected >10x speedup at S=%d R=%d W=%d, got %.1fx",
S, R, W, ratio);
System.out.printf("Test 4 (S=%d R=%d W=%d): slow=%d, fast=%d, speedup=%.1fx%n",
S, R, W, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}