java-topology/defects/kylin/unit/JobSchedulerAlgorithm.java

148 lines
6.6 KiB
Java

package unit;
import java.util.*;
import java.util.stream.*;
/**
* Standalone unit test for kylin-0001:
* JdbcJobScheduler — List<String>.contains() in stream filter → O(J²).
*
* Simulates the releaseJobLock() pattern:
* jobInfoIds collected as List<String>, then used in stream filter with .contains().
*
* Compile: javac -d . JobSchedulerAlgorithm.java
* Run: java unit.JobSchedulerAlgorithm
*/
public class JobSchedulerAlgorithm {
// ── Result ───────────────────────────────────────────────────────────────
static class Result {
final List<String> toRemoveLocks;
final long ns;
Result(List<String> locks, long ns) { this.toRemoveLocks = locks; this.ns = ns; }
}
// ── Defective: collects jobInfoIds into List → O(J) .contains per jobId ─
static class DefectiveAlgorithm {
/**
* @param jobIds all job IDs found in the lock table
* @param jobInfoIds job IDs that have corresponding JobInfo records (as List)
*/
Result releaseJobLock(List<String> jobIds, List<String> jobInfoIds) {
long t0 = System.nanoTime();
// Mirrors the defective code:
// List<String> jobInfoIds = jobs.stream().map(JobInfo::getJobId).collect(Collectors.toList());
// ... filter(jobId -> !jobInfoIds.contains(jobId)) ...
List<String> toRemoveLocks = jobIds.stream()
.filter(jobId -> !jobInfoIds.contains(jobId)) // CWE-407 site: O(J) per call
.collect(Collectors.toList());
return new Result(toRemoveLocks, System.nanoTime() - t0);
}
}
// ── Fixed: uses Set<String> → O(1) .contains ────────────────────────────
static class FixedAlgorithm {
Result releaseJobLock(List<String> jobIds, List<String> jobInfoIdsList) {
long t0 = System.nanoTime();
// Fix: collect into HashSet for O(1) membership
Set<String> jobInfoIds = new HashSet<>(jobInfoIdsList);
List<String> toRemoveLocks = jobIds.stream()
.filter(jobId -> !jobInfoIds.contains(jobId)) // O(1)
.collect(Collectors.toList());
return new Result(toRemoveLocks, System.nanoTime() - t0);
}
}
// ── Helpers ──────────────────────────────────────────────────────────────
static List<String> makeJobIds(int N) {
List<String> ids = new ArrayList<>(N);
for (int i = 0; i < N; i++) ids.add("job-" + i);
return ids;
}
// ── Assertions ───────────────────────────────────────────────────────────
static void assertEquals(Object expected, Object actual, String msg) {
if (!expected.equals(actual))
throw new AssertionError(msg + ": expected=" + expected + " actual=" + actual);
}
static void assertTrue(boolean cond, String msg) {
if (!cond) throw new AssertionError(msg);
}
public static void main(String[] args) {
System.out.println("=== kylin-0001: JobSchedulerAlgorithm ===");
DefectiveAlgorithm defAlg = new DefectiveAlgorithm();
FixedAlgorithm fixAlg = new FixedAlgorithm();
// Correctness test 1: some jobs have info, some don't
{
List<String> jobIds = makeJobIds(20);
// Only even-numbered jobs have a JobInfo record
List<String> jobInfoIds = jobIds.stream()
.filter(id -> Integer.parseInt(id.replace("job-", "")) % 2 == 0)
.collect(Collectors.toList());
Result dr = defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
Result fr = fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
// Sort both for comparison (order may differ)
List<String> dSorted = new ArrayList<>(dr.toRemoveLocks); Collections.sort(dSorted);
List<String> fSorted = new ArrayList<>(fr.toRemoveLocks); Collections.sort(fSorted);
assertEquals(dSorted, fSorted, "toRemoveLocks contents");
// All odd-numbered jobs should be in toRemoveLocks
assertTrue(dr.toRemoveLocks.size() == 10, "expected 10 orphan locks, got " + dr.toRemoveLocks.size());
System.out.println("PASS correctness (J=20, half missing)");
}
// Correctness test 2: all jobs have info records (no locks to remove)
{
List<String> jobIds = makeJobIds(10);
Result dr = defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobIds));
Result fr = fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobIds));
assertEquals(0, dr.toRemoveLocks.size(), "no orphan locks (defective)");
assertEquals(0, fr.toRemoveLocks.size(), "no orphan locks (fixed)");
System.out.println("PASS correctness all-present (J=10)");
}
// Benchmark at J=2000
{
int J = 2000;
List<String> jobIds = makeJobIds(J);
// Half the jobs have info records
List<String> jobInfoIds = jobIds.subList(0, J / 2);
// warm up
for (int i = 0; i < 5; i++) {
defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
}
long defNs = 0, fixNs = 0;
int reps = 30;
for (int i = 0; i < reps; i++) {
defNs += defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds)).ns;
fixNs += fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds)).ns;
}
defNs /= reps; fixNs /= reps;
double ratio = (double) defNs / Math.max(1, fixNs);
System.out.printf("BENCH J=%d reps=%d%n", J, reps);
System.out.printf(" defective avg: %,d ns%n", defNs);
System.out.printf(" fixed avg: %,d ns%n", fixNs);
System.out.printf(" speedup: %.1fx%n", ratio);
assertTrue(ratio >= 2.0, "Expected fixed >= 2x faster, got " + ratio + "x");
System.out.println("PASS speedup >= 2x");
}
System.out.println("=== ALL PASS ===");
}
}