import java.util.*; /** * Unit test for squid-0002: HttpHeader::removeConnectionHeaderEntries() * O(H * C) linear scan per response hop. * * Context: Called once per HTTP response in removeHopByHopEntries(), which runs * for every response forwarded by the proxy (client_side_reply.cc, Http1Server.cc). * * Defect: For each of H header entries, strListIsMember() scans all C tokens * in the Connection header string. Total: O(H * C) string comparisons per hop. * * Fix: Pre-build std::unordered_set from Connection tokens once (O(C)), then * probe O(1) per header entry. Total: O(C + H). * * At H=200 headers, C=50 Connection tokens: 10000 comparisons vs ~250 ops. */ public class SquidConnHeaderTest { // Defect: O(H * C) scan - strListIsMember called inside getEntry loop static int removeDefect(List headers, List connTokens) { int removed = 0; Iterator it = headers.iterator(); while (it.hasNext()) { String h = it.next(); // strListIsMember: iterate all C tokens for (String token : connTokens) { if (h.equalsIgnoreCase(token)) { it.remove(); removed++; break; } } } return removed; } // Fix: O(C + H) - pre-build HashSet from Connection tokens static int removeFix(List headers, List connTokens) { // Build set once: O(C) Set tokenSet = new HashSet<>(); for (String t : connTokens) tokenSet.add(t.toLowerCase(Locale.ROOT)); int removed = 0; Iterator it = headers.iterator(); while (it.hasNext()) { if (tokenSet.contains(it.next().toLowerCase(Locale.ROOT))) { it.remove(); removed++; } } return removed; } static List makeHeaders(int H, int matchCount) { List h = new ArrayList<>(); for (int i = 0; i < H - matchCount; i++) h.add("X-Custom-Header-" + i); for (int i = 0; i < matchCount; i++) h.add("conn-token-" + i); Collections.shuffle(h, new Random(42)); return h; } static List makeTokens(int C) { List t = new ArrayList<>(); for (int i = 0; i < C; i++) t.add("conn-token-" + i); return t; } public static void main(String[] args) { System.out.println("=== squid-0002: HttpHeader::removeConnectionHeaderEntries O(H*C) ==="); System.out.println(); // --- Correctness --- List headers = Arrays.asList( "Content-Type", "Keep-Alive", "Transfer-Encoding", "Upgrade", "X-Custom", "Authorization", "Accept"); List connTokens = Arrays.asList("keep-alive", "upgrade", "transfer-encoding"); List dh = new ArrayList<>(headers); int dRemoved = removeDefect(dh, connTokens); List fh = new ArrayList<>(headers); int fRemoved = removeFix(fh, connTokens); assert dRemoved == 3 : "defect: expected 3 removed, got " + dRemoved; assert fRemoved == 3 : "fix: expected 3 removed, got " + fRemoved; assert dh.equals(fh) : "result mismatch: " + dh + " vs " + fh; System.out.println("Correctness: PASS (both removed " + dRemoved + " hop-by-hop headers)"); System.out.println(); // --- Performance: adversarial case (H=200, C=50) --- int H = 200, C = 50, match = 20; int iters = 100_000; List baseHeaders = makeHeaders(H, match); List tokens = makeTokens(C); // Warmup for (int i = 0; i < 5000; i++) { removeDefect(new ArrayList<>(baseHeaders), tokens); removeFix(new ArrayList<>(baseHeaders), tokens); } long t0 = System.nanoTime(); int totalD = 0; for (int i = 0; i < iters; i++) totalD += removeDefect(new ArrayList<>(baseHeaders), tokens); long defectNs = System.nanoTime() - t0; long t1 = System.nanoTime(); int totalF = 0; for (int i = 0; i < iters; i++) totalF += removeFix(new ArrayList<>(baseHeaders), tokens); long fixNs = System.nanoTime() - t1; assert totalD == totalF : "removed count mismatch: " + totalD + " vs " + totalF; double ratio = (double) defectNs / fixNs; System.out.printf("H=%d C=%d match=%d iters=%d%n", H, C, match, iters); System.out.printf(" defect: %,d ns total (%,d ns/iter)%n", defectNs, defectNs / iters); System.out.printf(" fix: %,d ns total (%,d ns/iter)%n", fixNs, fixNs / iters); System.out.printf(" speedup: %.2fx%n%n", ratio); // At H=200, C=50 the defect does 200*50=10000 comparisons per call; // the fix does 50+200=250. Even with JVM overhead, expect >= 2x. assert ratio >= 2.0 : "Expected >= 2x speedup at H=" + H + " C=" + C + ", got " + ratio + "x"; System.out.println("Performance: PASS"); System.out.println("=== squid-0002 PASS ==="); } }