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)
190 lines
6.9 KiB
Java
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);
|
|
}
|
|
}
|