java-topology/defects/pulsar/unit/Pulsar0007ModularLoadMgrTest.java
russell@unturf.com a922a7ee9d thrift-0002 + victoria-metrics-0002 + pulsar-0007 unit + kafka-0010 fix
thrift-0002: t_cpp_generator::is_struct_storage_not_throwing() vector<t_field*>
  member deduplication uses std::find O(M²) — fix with unordered_set

victoria-metrics-0002: MetricName RemoveTagsOn/RemoveTagsIgnoring hasTag()
  O(T×I) linear scan inside per-metric loop — fix with map-based tag set

pulsar-0007: ModularLoadManagerImpl.reapDeadBrokerPreallocations() takes
  List<String> aliveBrokers, calls contains() O(B) per broker — fix with HashSet

kafka-0010: fix unit test worst-case ordering (shared-topic last for slow path)
2026-03-29 22:25:29 -04:00

190 lines
6.9 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CWE-407 unit test: pulsar-0007
*
* Models ModularLoadManagerImpl.reapDeadBrokerPreallocations(List<String> aliveBrokers).
*
* DEFECT: The method takes List<String> aliveBrokers from listLocks().
* For each broker in loadData.getBrokerData().keySet(),
* it calls aliveBrokers.contains(broker) — O(B) per iteration.
* Total: O(B²) triggered on every broker metadata notification.
*
* FIX: Convert aliveBrokers to HashSet<String> at method entry.
* contains() becomes O(1); total: O(B).
*
* Asserts: slowOps > fastOps * 10 at B=500 brokers.
*/
public class Pulsar0007ModularLoadMgrTest {
/**
* Simulates defective reapDeadBrokerPreallocations.
* aliveBrokers is a List<String> — contains() is O(B).
*/
static long slow(int numBrokers, int numDead) {
// numBrokers total, numDead are dead (not in alive list)
List<String> aliveBrokers = new ArrayList<>();
Set<String> allBrokerSet = new HashSet<>();
for (int i = numDead; i < numBrokers; i++) {
String broker = "broker-" + i + ":8080";
aliveBrokers.add(broker);
}
// All brokers in loadData (both alive and dead)
List<String> allBrokers = new ArrayList<>();
for (int i = 0; i < numBrokers; i++) {
allBrokers.add("broker-" + i + ":8080");
}
long ops = 0;
for (String broker : allBrokers) {
// O(alive.size()) scan — the defect
for (String alive : aliveBrokers) {
ops++;
if (alive.equals(broker)) break;
// if not found, we scan the entire list
}
// In the real code, also checks contains() returning false for dead brokers
// For dead brokers, the full list is scanned
}
return ops;
}
/**
* Simulates the patched version.
* Converts aliveBrokers List to HashSet at method entry.
*/
static long fast(int numBrokers, int numDead) {
List<String> aliveBrokersList = new ArrayList<>();
for (int i = numDead; i < numBrokers; i++) {
aliveBrokersList.add("broker-" + i + ":8080");
}
Set<String> aliveBrokersSet = new HashSet<>(aliveBrokersList); // O(B) once
List<String> allBrokers = new ArrayList<>();
for (int i = 0; i < numBrokers; i++) {
allBrokers.add("broker-" + i + ":8080");
}
long ops = 0;
for (String broker : allBrokers) {
ops++; // O(1) hash probe
aliveBrokersSet.contains(broker);
}
return ops;
}
/**
* Count accurate dead-broker detections (correctness check).
*/
static int countDead(List<String> all, List<String> alive) {
int count = 0;
for (String broker : all) {
if (!alive.contains(broker)) count++;
}
return count;
}
static int countDeadFast(List<String> all, List<String> alive) {
Set<String> aliveSet = new HashSet<>(alive);
int count = 0;
for (String broker : all) {
if (!aliveSet.contains(broker)) count++;
}
return count;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: 200 brokers, 20 dead
{
total++;
int B = 200, D = 20;
long sOps = slow(B, D);
long fOps = fast(B, D);
// slow: for each of B brokers, scans up to (B-D) alive brokers = ~36000
// fast: B ops = 200
boolean ok = sOps > fOps * 10L;
System.out.printf("Test 1 [B=%d dead=%d slow=%d fast=%d ratio=%.1fx]: %s%n",
B, D, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: 500 brokers, 50 dead
{
total++;
int B = 500, D = 50;
long sOps = slow(B, D);
long fOps = fast(B, D);
boolean ok = sOps > fOps * 50L;
System.out.printf("Test 2 [B=%d dead=%d slow=%d fast=%d ratio=%.1fx]: %s%n",
B, D, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: worst case — all brokers are dead (full scan per check)
{
total++;
int B = 300;
List<String> alive = new ArrayList<>(); // empty — all dead
List<String> all = new ArrayList<>();
for (int i = 0; i < B; i++) all.add("broker-" + i);
long sOps = 0;
for (String broker : all) {
// contains on empty list — 0 ops but it returns immediately
// Simulate non-empty alive list where none match
for (String a : alive) { sOps++; if (a.equals(broker)) break; }
sOps++; // simulate the contains() call cost even for empty
}
// Use a more interesting case: alive list has B/2 different brokers
List<String> aliveHalf = new ArrayList<>();
for (int i = B; i < B + B / 2; i++) aliveHalf.add("broker-" + i);
List<String> allB = new ArrayList<>();
for (int i = 0; i < B; i++) allB.add("broker-" + i);
long slowOps = 0;
for (String broker : allB) {
for (String a : aliveHalf) { slowOps++; if (a.equals(broker)) break; }
}
long fastOps = 0;
Set<String> aliveSet = new HashSet<>(aliveHalf);
for (String broker : allB) { fastOps++; aliveSet.contains(broker); }
boolean ok = slowOps > fastOps * 50L;
System.out.printf("Test 3 [B=%d all-dead slow=%d fast=%d ratio=%.1fx]: %s%n",
B, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 4: correctness — same dead broker detection
{
total++;
int B = 100, D = 10;
List<String> all = new ArrayList<>();
List<String> alive = new ArrayList<>();
for (int i = 0; i < B; i++) all.add("broker-" + i);
for (int i = D; i < B; i++) alive.add("broker-" + i);
int slowDead = countDead(all, alive);
int fastDead = countDeadFast(all, alive);
boolean ok = slowDead == D && fastDead == D && slowDead == fastDead;
System.out.printf("Test 4 [correctness dead_expected=%d slow=%d fast=%d equal=%b]: %s%n",
D, slowDead, fastDead, slowDead == fastDead, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}