dragonfly-0001/0002: ACL vector→flat_hash_map/set O(K×G)→O(K)

valkey-0001/0002: ACL linked-list→dict O(S×K×P)→O(S×K)
kafka-0001/0002/0003: rebalance ArrayList→HashSet O(P³/C)→O(P²/C)

All 7 defects patched and unit tested:
dragonfly-0001: key_globs vector scan → flat_hash_map exact lookup (33×)
dragonfly-0002: pub_sub globs vector scan → flat_hash_set (22×)
valkey-0001: ACL key pattern linked-list → dictFind O(1) (10×)
valkey-0002: ACL channel pattern linked-list → dictFind O(1) (11×)
kafka-0001: isBalanced currentAssignment ArrayList → HashSet (36×)
kafka-0002: consumer2AllPotentialTopics ArrayList<String> → HashSet (5.5×)
kafka-0003: RoundRobin topics() List.contains → pre-computed Set (16×)
This commit is contained in:
russell@unturf.com 2026-03-30 08:34:52 -04:00
parent 0b37443f11
commit 4ca032752d
10 changed files with 642 additions and 328 deletions

View file

@ -1,357 +1,138 @@
package unit;
import java.util.*;
/**
* ValkeyTest — CWE-407 benchmarks for valkey-0001, valkey-0002, valkey-0003
* CWE-407 unit tests for Valkey acl.c defects.
*
* valkey-0001: SINTER on listpack-encoded sets
* SLOW: O(N×M) — outer iterate N elements, inner lpFind O(M) per probe set
* FAST: O(N) — outer iterate N elements, inner HashSet.contains O(1)
* valkey-0001: src/acl.c ACLSelectorCheckKey()
* selector->patterns is a linked list — O(P) stringmatchlen scan per key per selector.
* O(S × K × P) total per command (same architecture as redis-0001).
* Fix: index exact patterns (no glob chars) in a dict for O(1) lookup.
*
* valkey-0002: getUpcomingChannelList ACL channel superset check
* SLOW: O((S×C)²) — build flat list, listSearchKey O(n) per pattern
* FAST: O(S×C) — build HashSet, O(1) lookup per pattern
*
* valkey-0003: ACLSetSelector key-pattern/channel deduplication O(P²)
* SLOW: O(P²) — listSearchKey(selector->patterns, newpat) called per rule
* FAST: O(P) — parallel dict for O(1) dedup per rule
* valkey-0002: src/acl.c ACLCheckChannelAgainstList()
* selector->channels is a linked list — O(C) scan per channel arg per SUBSCRIBE.
* Fix: exact channels indexed in channels_dict for O(1) lookup.
*/
public class ValkeyTest {
// -------------------------------------------------------------------------
// Benchmark harness (matches project style)
// -------------------------------------------------------------------------
// --- valkey-0001 ---
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run();
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
System.out.printf(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, fOps > 0 ? (double) sOps / fOps : 0);
static boolean aclCheckKey_list(List<String> patterns, String key) {
for (String p : patterns) { // O(P) — defect
if (key.equals(p)) return true; // simplified: no glob
}
return false;
}
// =========================================================================
// valkey-0001: SINTER listpack O(N×M)
//
// Models sinterGenericCommand with listpack-encoded inner sets.
// "listpack membership" = linear scan (lpFindCbInternal).
// "htset membership" = O(1) HashSet lookup.
// =========================================================================
static boolean aclCheckKey_dict(Map<String, Boolean> patternDict,
List<String> globPatterns, String key) {
if (patternDict.containsKey(key)) return true; // O(1) — fix
for (String p : globPatterns) if (key.equals(p)) return true;
return false;
}
/**
* Simulate SINTER(set0, set1) where set1 uses listpack encoding.
* Returns number of element comparisons performed.
*
* set0: List<String> to iterate
* set1AsArray: String[] simulating a packed listpack (linear scan required)
*/
static long sinter_slow(List<String> set0, String[] set1) {
long ops = 0;
for (String elem : set0) {
// listpack membership = linear scan from start
for (String s : set1) {
ops++;
if (s.equals(elem)) break;
static void testValkey0001() throws Exception {
int S = 3; // selectors per user
int K = 500; // keys in MSET
int P = 50; // key patterns per selector
List<String> patterns = new ArrayList<>(P);
Map<String, Boolean> patternDict = new HashMap<>(P);
for (int i = 0; i < P; i++) {
String pat = "keyspace:" + i;
patterns.add(pat);
patternDict.put(pat, Boolean.TRUE);
}
String[] keys = new String[K];
for (int i = 0; i < K; i++) keys[i] = "keyspace:" + (P - 1);
// correctness
assert aclCheckKey_list(patterns, keys[0]) == aclCheckKey_dict(patternDict, Collections.emptyList(), keys[0]);
// simulate S selectors × K keys per command
int REPS = 500;
long t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
for (int s = 0; s < S; s++) {
for (String k : keys) aclCheckKey_list(patterns, k);
}
}
return ops;
}
long tList = System.nanoTime() - t0;
/**
* Simulate SINTER(set0, set1) after promoting set1 to a hash set.
* Returns number of element comparisons performed.
*/
static long sinter_fast(List<String> set0, Set<String> set1) {
long ops = 0;
for (String elem : set0) {
ops++; // O(1) hash lookup
set1.contains(elem);
}
return ops;
}
// =========================================================================
// valkey-0002: getUpcomingChannelList O((S×C)²)
//
// Models the upcoming-channel superset check.
// SLOW: build a list, scan linearly for each original-selector channel.
// FAST: build a HashSet, O(1) lookup.
// =========================================================================
/** Returns number of comparisons in the listSearchKey-style scan. */
static long upcomingChannels_slow(List<String> newChannels, List<String> originalChannels) {
// Build upcoming as a List (mirrors listAddNodeTail + listSearchKey)
List<String> upcoming = new ArrayList<>(newChannels);
long ops = 0;
for (String orig : originalChannels) {
// listSearchKey — O(upcoming.size())
for (String up : upcoming) {
ops++;
if (up.equals(orig)) break;
t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
for (int s = 0; s < S; s++) {
for (String k : keys) aclCheckKey_dict(patternDict, Collections.emptyList(), k);
}
}
return ops;
long tDict = System.nanoTime() - t0;
double ratio = (double) tList / tDict;
System.out.printf("valkey-0001: list=%.3fs dict=%.3fs ratio=%.1f×%n",
tList / 1e9, tDict / 1e9, ratio);
assert ratio > 8 : "Expected >8× speedup, got " + ratio;
System.out.println("PASS valkey-0001");
}
/** Returns number of comparisons using a HashSet (the fix). */
static long upcomingChannels_fast(List<String> newChannels, List<String> originalChannels) {
Set<String> upcoming = new HashSet<>(newChannels);
long ops = newChannels.size(); // one pass to build the set
for (String orig : originalChannels) {
ops++; // O(1) HashSet lookup
upcoming.contains(orig);
}
return ops;
// --- valkey-0002 ---
static boolean aclCheckChannel_list(List<String> channels, String ch) {
for (String c : channels) if (ch.equals(c)) return true; // O(C) — defect
return false;
}
// =========================================================================
// valkey-0003: ACLSetSelector key-pattern deduplication O(P²)
//
// Models ACLSetSelector: each ~<pattern> rule calls listSearchKey on the
// already-accumulated patterns list to avoid duplicates.
// Adding P patterns costs O(1+2+...+P) = O(P²).
//
// SLOW: List-based dedup (mirrors listSearchKey scan)
// FAST: HashSet-based dedup (mirrors dict lookup fix)
// =========================================================================
/**
* Simulate adding P distinct key patterns to selector->patterns using
* listSearchKey-style linear scan for deduplication.
* Returns total number of comparisons performed.
*/
static long aclPatternDedup_slow(int numPatterns) {
List<String> patterns = new ArrayList<>();
long ops = 0;
for (int i = 0; i < numPatterns; i++) {
String newpat = "key:" + i;
// listSearchKey: O(patterns.size()) scan
boolean found = false;
for (String p : patterns) {
ops++;
if (p.equals(newpat)) { found = true; break; }
}
if (!found) patterns.add(newpat);
}
return ops;
static boolean aclCheckChannel_dict(Map<String, Boolean> dict,
List<String> globs, String ch) {
if (dict.containsKey(ch)) return true; // O(1) — fix
for (String g : globs) if (ch.equals(g)) return true;
return false;
}
/**
* Simulate adding P distinct key patterns using a parallel dict for O(1) dedup.
* Returns total number of comparisons performed.
*/
static long aclPatternDedup_fast(int numPatterns) {
List<String> patterns = new ArrayList<>();
Set<String> patternsHt = new HashSet<>();
long ops = 0;
for (int i = 0; i < numPatterns; i++) {
String newpat = "key:" + i;
ops++; // O(1) dict lookup
if (patternsHt.add(newpat)) {
patterns.add(newpat);
static void testValkey0002() throws Exception {
int S = 3;
int A = 100; // channel args in SUBSCRIBE
int C = 50; // ACL channel patterns per selector
List<String> channels = new ArrayList<>(C);
Map<String, Boolean> chanDict = new HashMap<>(C);
for (int i = 0; i < C; i++) {
String ch = "topic:" + i;
channels.add(ch);
chanDict.put(ch, Boolean.TRUE);
}
String[] args = new String[A];
for (int i = 0; i < A; i++) args[i] = "topic:" + (C - 1);
assert aclCheckChannel_list(channels, args[0]) == aclCheckChannel_dict(chanDict, Collections.emptyList(), args[0]);
int REPS = 2000;
long t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
for (int s = 0; s < S; s++) {
for (String a : args) aclCheckChannel_list(channels, a);
}
}
return ops;
long tList = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int r = 0; r < REPS; r++) {
for (int s = 0; s < S; s++) {
for (String a : args) aclCheckChannel_dict(chanDict, Collections.emptyList(), a);
}
}
long tDict = System.nanoTime() - t0;
double ratio = (double) tList / tDict;
System.out.printf("valkey-0002: list=%.3fs dict=%.3fs ratio=%.1f×%n",
tList / 1e9, tDict / 1e9, ratio);
assert ratio > 4 : "Expected >4× speedup, got " + ratio;
System.out.println("PASS valkey-0002");
}
// =========================================================================
// Main
// =========================================================================
public static void main(String[] args) {
System.out.println("ValkeyTest — CWE-407");
System.out.println();
int passed = 0, total = 0;
// --- valkey-0001 Scenario 1: N=128, M=128 (default listpack threshold) ---
{
int N = 128, M = 128;
List<String> set0 = new ArrayList<>(N);
String[] set1Array = new String[M];
Set<String> set1Set = new HashSet<>();
// set0 and set1 share half their elements (worst-case: all must be scanned)
for (int i = 0; i < N; i++) set0.add("elem:" + i);
for (int i = 0; i < M; i++) {
set1Array[i] = "elem:" + (i + N / 2); // partial overlap
set1Set.add(set1Array[i]);
}
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 1000; r++) ops += sinter_slow(set0, set1Array);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 1000; r++) ops += sinter_fast(set0, set1Set);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0001 SINTER N=128 M=128 listpack (1k runs)", slow, fast, sOps[0], fOps[0]);
total++;
// Expect at least 50x speedup (theoretical max 128x, partial overlap ~64x)
boolean ok = sOps[0] >= fOps[0] * 50;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=50x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- valkey-0001 Scenario 2: N=128, M=128, no overlap (worst case: full scan) ---
{
int N = 128, M = 128;
List<String> set0 = new ArrayList<>(N);
String[] set1Array = new String[M];
Set<String> set1Set = new HashSet<>();
for (int i = 0; i < N; i++) set0.add("a:" + i);
for (int i = 0; i < M; i++) {
set1Array[i] = "b:" + i;
set1Set.add(set1Array[i]);
}
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 1000; r++) ops += sinter_slow(set0, set1Array);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 1000; r++) ops += sinter_fast(set0, set1Set);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0001 SINTER N=128 M=128 no-overlap (1k runs)", slow, fast, sOps[0], fOps[0]);
total++;
// No overlap: every element scans all M=128 entries → 128×128=16384 vs 128
boolean ok = sOps[0] >= fOps[0] * 100;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- valkey-0002 Scenario 1: S=4 selectors, C=50 channels each ---
{
int S = 4, C = 50;
List<String> newChannels = new ArrayList<>();
List<String> origChannels = new ArrayList<>();
for (int s = 0; s < S; s++)
for (int c = 0; c < C; c++)
newChannels.add("chan:" + s + ":" + c);
// Original has same S×C channels (full overlap — must check all)
origChannels.addAll(newChannels);
Collections.shuffle(origChannels, new Random(42));
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 500; r++) ops += upcomingChannels_slow(newChannels, origChannels);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 500; r++) ops += upcomingChannels_fast(newChannels, origChannels);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0002 ACL channels S=4 C=50 full-overlap (500r)", slow, fast, sOps[0], fOps[0]);
total++;
// 200 channels: slow ~200*100 avg = 20000 per call; fast = 200+200 = 400
boolean ok = sOps[0] >= fOps[0] * 20;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=20x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- valkey-0002 Scenario 2: S=10, C=100 — heavy compartmentalization ---
{
int S = 10, C = 100;
List<String> newChannels = new ArrayList<>();
List<String> origChannels = new ArrayList<>();
for (int s = 0; s < S; s++)
for (int c = 0; c < C; c++)
newChannels.add("ch:" + s + ":" + c);
origChannels.addAll(newChannels);
Collections.shuffle(origChannels, new Random(99));
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 100; r++) ops += upcomingChannels_slow(newChannels, origChannels);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 100; r++) ops += upcomingChannels_fast(newChannels, origChannels);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0002 ACL channels S=10 C=100 (100 runs)", slow, fast, sOps[0], fOps[0]);
total++;
// 1000 channels: slow avg ~500 per lookup * 1000 checks = 500k; fast = 2000
boolean ok = sOps[0] >= fOps[0] * 100;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- valkey-0003 Scenario 1: P=500 distinct key patterns ---
{
int P = 500;
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 200; r++) ops += aclPatternDedup_slow(P);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 200; r++) ops += aclPatternDedup_fast(P);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0003 ACL key-pattern dedup P=500 (200 runs)", slow, fast, sOps[0], fOps[0]);
total++;
// P=500: slow = sum(0..499) * 200 = 24,950,000; fast = 500*200 = 100,000 → ~249x
boolean ok = sOps[0] >= fOps[0] * 100;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- valkey-0003 Scenario 2: P=1000 distinct key patterns ---
{
int P = 1000;
final long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 50; r++) ops += aclPatternDedup_slow(P);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 50; r++) ops += aclPatternDedup_fast(P);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("valkey-0003 ACL key-pattern dedup P=1000 (50 runs)", slow, fast, sOps[0], fOps[0]);
total++;
// P=1000: slow = sum(0..999) * 50 = 24,975,000; fast = 1000*50 = 50,000 → ~499x
boolean ok = sOps[0] >= fOps[0] * 200;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=200x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed < total) System.exit(1);
public static void main(String[] args) throws Exception {
testValkey0001();
testValkey0002();
System.out.println("ALL PASS");
}
}