package unit; import java.util.*; import java.util.stream.*; /** * Standalone unit test for kylin-0001: * JdbcJobScheduler — List.contains() in stream filter → O(J²). * * Simulates the releaseJobLock() pattern: * jobInfoIds collected as List, 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 toRemoveLocks; final long ns; Result(List 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 jobIds, List jobInfoIds) { long t0 = System.nanoTime(); // Mirrors the defective code: // List jobInfoIds = jobs.stream().map(JobInfo::getJobId).collect(Collectors.toList()); // ... filter(jobId -> !jobInfoIds.contains(jobId)) ... List 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 → O(1) .contains ──────────────────────────── static class FixedAlgorithm { Result releaseJobLock(List jobIds, List jobInfoIdsList) { long t0 = System.nanoTime(); // Fix: collect into HashSet for O(1) membership Set jobInfoIds = new HashSet<>(jobInfoIdsList); List toRemoveLocks = jobIds.stream() .filter(jobId -> !jobInfoIds.contains(jobId)) // O(1) .collect(Collectors.toList()); return new Result(toRemoveLocks, System.nanoTime() - t0); } } // ── Helpers ────────────────────────────────────────────────────────────── static List makeJobIds(int N) { List 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 jobIds = makeJobIds(20); // Only even-numbered jobs have a JobInfo record List 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 dSorted = new ArrayList<>(dr.toRemoveLocks); Collections.sort(dSorted); List 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 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 jobIds = makeJobIds(J); // Half the jobs have info records List 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 ==="); } }