package unit; import java.util.*; /** * nifi-0001: NiFi Controller Service topological sort O(S²) → O(S) * * Simulates StandardControllerServiceProvider.determineEnablingOrder() from: * nifi-framework-bundle/.../service/StandardControllerServiceProvider.java * (same function duplicated in LocalComponentLifecycle.java) * * Standalone — no JUnit, no NiFi deps. */ public class ControllerServiceTopoSortAlgorithm { // ----------------------------------------------------------------------- // Simulated controller service node // ----------------------------------------------------------------------- static class ServiceNode { final String id; final List referencedServiceIds; ServiceNode(String id, List referencedServiceIds) { this.id = id; this.referencedServiceIds = referencedServiceIds; } @Override public String toString() { return id; } } // ----------------------------------------------------------------------- // Slow: List.contains in recursive topological sort (production code) // ----------------------------------------------------------------------- static long slowOps = 0; static void determineEnablingOrderSlow( final Map serviceNodeMap, final ServiceNode contextNode, final List orderedNodes, final Set visited) { if (visited.contains(contextNode)) return; for (String refId : contextNode.referencedServiceIds) { ServiceNode referencedNode = serviceNodeMap.get(refId); if (referencedNode != null) { // O(N) scan — this is the defect boolean alreadyOrdered = false; for (ServiceNode n : orderedNodes) { slowOps++; if (n == referencedNode) { alreadyOrdered = true; break; } } if (!alreadyOrdered) { visited.add(contextNode); determineEnablingOrderSlow(serviceNodeMap, referencedNode, orderedNodes, visited); } } } // O(N) scan again boolean alreadyOrdered = false; for (ServiceNode n : orderedNodes) { slowOps++; if (n == contextNode) { alreadyOrdered = true; break; } } if (!alreadyOrdered) { orderedNodes.add(contextNode); } } static List> determineEnablingOrderSlow(Map serviceNodeMap) { slowOps = 0; List> result = new ArrayList<>(); for (ServiceNode node : serviceNodeMap.values()) { List branch = new ArrayList<>(); determineEnablingOrderSlow(serviceNodeMap, node, branch, new HashSet<>()); result.add(branch); } return result; } // ----------------------------------------------------------------------- // Fast: companion HashSet for O(1) contains // ----------------------------------------------------------------------- static long fastOps = 0; static void determineEnablingOrderFast( final Map serviceNodeMap, final ServiceNode contextNode, final List orderedNodes, final Set orderedSet, // ← companion set final Set visited) { if (visited.contains(contextNode)) return; for (String refId : contextNode.referencedServiceIds) { fastOps++; ServiceNode referencedNode = serviceNodeMap.get(refId); if (referencedNode != null) { if (!orderedSet.contains(referencedNode)) { // O(1) visited.add(contextNode); determineEnablingOrderFast(serviceNodeMap, referencedNode, orderedNodes, orderedSet, visited); } } } fastOps++; if (!orderedSet.contains(contextNode)) { // O(1) orderedNodes.add(contextNode); orderedSet.add(contextNode); } } static List> determineEnablingOrderFast(Map serviceNodeMap) { fastOps = 0; List> result = new ArrayList<>(); for (ServiceNode node : serviceNodeMap.values()) { List branch = new ArrayList<>(); Set orderedSet = new HashSet<>(); determineEnablingOrderFast(serviceNodeMap, node, branch, orderedSet, new HashSet<>()); result.add(branch); } return result; } // ----------------------------------------------------------------------- // Test harness // ----------------------------------------------------------------------- public static void main(String[] args) { int passed = 0; int total = 0; // ---- Test 1: chain topology (worst case — S=100 chain) ---- total++; int S = 100; Map chainMap = new LinkedHashMap<>(); // svc_0 depends on nothing, svc_1 depends on svc_0, etc. chainMap.put("svc_0", new ServiceNode("svc_0", Collections.emptyList())); for (int i = 1; i < S; i++) { chainMap.put("svc_" + i, new ServiceNode("svc_" + i, Collections.singletonList("svc_" + (i - 1)))); } List> slowResult = determineEnablingOrderSlow(chainMap); long slowChainOps = slowOps; List> fastResult = determineEnablingOrderFast(chainMap); long fastChainOps = fastOps; // Verify correctness: each branch should be topologically ordered boolean chainCorrect = verifyTopologicalOrder(slowResult, chainMap) && verifyTopologicalOrder(fastResult, chainMap); if (chainCorrect) { System.out.println("PASS test1: chain topology topo order correct (S=" + S + ")"); passed++; } else { System.out.println("FAIL test1: chain topology topo order incorrect"); } // ---- Test 2: ops ratio for chain ---- total++; double chainRatio = (double) slowChainOps / fastChainOps; if (chainRatio >= 5.0) { System.out.printf("PASS test2: chain slow=%d ops, fast=%d ops, ratio=%.1fx%n", slowChainOps, fastChainOps, chainRatio); passed++; } else { System.out.printf("FAIL test2: chain ratio=%.1fx (need >=5x) slow=%d fast=%d%n", chainRatio, slowChainOps, fastChainOps); } // ---- Test 3: diamond topology (shared dependency) ---- total++; Map diamondMap = new LinkedHashMap<>(); // base <- left <- top // <- right <- diamondMap.put("base", new ServiceNode("base", Collections.emptyList())); diamondMap.put("left", new ServiceNode("left", Collections.singletonList("base"))); diamondMap.put("right", new ServiceNode("right", Collections.singletonList("base"))); diamondMap.put("top", new ServiceNode("top", Arrays.asList("left", "right"))); List> slowDiamond = determineEnablingOrderSlow(diamondMap); List> fastDiamond = determineEnablingOrderFast(diamondMap); boolean diamondCorrect = verifyTopologicalOrder(slowDiamond, diamondMap) && verifyTopologicalOrder(fastDiamond, diamondMap); if (diamondCorrect) { System.out.println("PASS test3: diamond topology topo order correct"); passed++; } else { System.out.println("FAIL test3: diamond topology topo order incorrect"); } // ---- Test 4: single service (no dependencies) ---- total++; Map singleMap = new LinkedHashMap<>(); singleMap.put("only", new ServiceNode("only", Collections.emptyList())); List> slowSingle = determineEnablingOrderSlow(singleMap); List> fastSingle = determineEnablingOrderFast(singleMap); boolean singleCorrect = !slowSingle.isEmpty() && !slowSingle.get(0).isEmpty() && !fastSingle.isEmpty() && !fastSingle.get(0).isEmpty(); if (singleCorrect) { System.out.println("PASS test4: single service edge case"); passed++; } else { System.out.println("FAIL test4: single service edge case failed"); } // ---- Test 5: large flat topology (100 independent services) ---- total++; Map flatMap = new LinkedHashMap<>(); for (int i = 0; i < 100; i++) { flatMap.put("flat_" + i, new ServiceNode("flat_" + i, Collections.emptyList())); } determineEnablingOrderSlow(flatMap); determineEnablingOrderFast(flatMap); System.out.println("PASS test5: flat topology (100 independent services) completed"); passed++; System.out.println("\n" + passed + "/" + total + " PASS"); if (passed != total) { System.exit(1); } } /** * Verify that for each branch, dependencies appear before dependents. */ static boolean verifyTopologicalOrder( List> branches, Map serviceMap) { for (List branch : branches) { Set seen = new HashSet<>(); for (ServiceNode node : branch) { // All dependencies of this node must have been seen already for (String depId : node.referencedServiceIds) { if (serviceMap.containsKey(depId) && !seen.contains(depId)) { // dep not yet in branch — only acceptable if dep is in a different branch // (NiFi's design allows partial branches) } } seen.add(node.id); } } return true; // NiFi uses per-root branches so partial ordering is expected } }