java-topology/defects/kafka/unit/KafkaStreamsDefaultTaskManagerTest.java

130 lines
4.4 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.*;
/**
* kafka-0006: DefaultTaskManager lockedTasks ArrayList → HashSet
*
* Demonstrates that ArrayList.contains() inside a task-scheduling loop is O(T×L)
* while HashSet.contains() is O(T).
*
* Compile: javac -d . KafkaStreamsDefaultTaskManagerTest.java
* Run: java unit.KafkaStreamsDefaultTaskManagerTest
*/
public class KafkaStreamsDefaultTaskManagerTest {
// Simulated TaskId (Integer wrapper for simplicity)
static class TaskId {
final int id;
TaskId(int id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof TaskId && ((TaskId) o).id == this.id;
}
@Override public int hashCode() { return Integer.hashCode(id); }
@Override public String toString() { return "Task-" + id; }
}
// --- SLOW: ArrayList lockedTasks (defective, mirrors DefaultTaskManager line 62) ---
static class DefectiveTaskManager {
private final List<TaskId> lockedTasks = new ArrayList<>();
private final List<TaskId> activeTasks;
DefectiveTaskManager(List<TaskId> activeTasks) {
this.activeTasks = activeTasks;
}
void lockTasks(Set<TaskId> ids) { lockedTasks.addAll(ids); }
// Returns count of assignable tasks — mirrors assignNextTask() loop body
int countAssignable() {
int count = 0;
for (TaskId task : activeTasks) {
if (!lockedTasks.contains(task)) { // O(L) ArrayList scan
count++;
}
}
return count;
}
}
// --- FAST: HashSet lockedTasks (fixed) ---
static class FixedTaskManager {
private final Set<TaskId> lockedTasks = new HashSet<>();
private final List<TaskId> activeTasks;
FixedTaskManager(List<TaskId> activeTasks) {
this.activeTasks = activeTasks;
}
void lockTasks(Set<TaskId> ids) { lockedTasks.addAll(ids); }
int countAssignable() {
int count = 0;
for (TaskId task : activeTasks) {
if (!lockedTasks.contains(task)) { // O(1) HashSet lookup
count++;
}
}
return count;
}
}
static long bench(Runnable r, int iters) {
// warmup
for (int i = 0; i < 3; i++) r.run();
long t0 = System.nanoTime();
for (int i = 0; i < iters; i++) r.run();
return System.nanoTime() - t0;
}
public static void main(String[] args) {
System.out.println("kafka-0006: DefaultTaskManager lockedTasks ArrayList → HashSet");
System.out.println("=".repeat(65));
int[] sizes = {100, 500, 1000};
int iters = 500;
boolean allPass = true;
for (int n : sizes) {
// Build n active tasks, lock half
List<TaskId> active = new ArrayList<>();
Set<TaskId> toLock = new HashSet<>();
for (int i = 0; i < n; i++) {
TaskId t = new TaskId(i);
active.add(t);
if (i % 2 == 0) toLock.add(t);
}
DefectiveTaskManager slow = new DefectiveTaskManager(active);
slow.lockTasks(toLoad(toLoad(toLock)));
FixedTaskManager fast = new FixedTaskManager(active);
fast.lockTasks(toLoad(toLock));
// Correctness check
int slowResult = slow.countAssignable();
int fastResult = fast.countAssignable();
boolean correct = (slowResult == fastResult) && (slowResult == n / 2);
if (!correct) allPass = false;
long slowNs = bench(slow::countAssignable, iters);
long fastNs = bench(fast::countAssignable, iters);
double ratio = (double) slowNs / fastNs;
System.out.printf("N=%-5d slow=%7.2f ms fast=%7.2f ms ratio=%.1fx assignable=%d %s%n",
n,
slowNs / 1_000_000.0 / iters,
fastNs / 1_000_000.0 / iters,
ratio,
slowResult,
correct ? "PASS" : "FAIL(result mismatch)");
}
System.out.println("=".repeat(65));
System.out.println(allPass ? "ALL PASS" : "SOME FAILED");
if (!allPass) System.exit(1);
}
// Helper: copy set into set (simulate addAll contract)
static Set<TaskId> toLoad(Set<TaskId> s) { return s; }
}