esp-idf: 2 CWE-312 defects (WiFi PSK + HTTP auth password logged); drone: CWE-407 pubsub O(S*T) + MOAD-0005 thundering herd; MOADs 0002/0003 CLEAN
This commit is contained in:
parent
ad90dddbcc
commit
bc17a5e700
10 changed files with 883 additions and 0 deletions
200
defects/drone-0002/test/Drone0002Test.java
Normal file
200
defects/drone-0002/test/Drone0002Test.java
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
* Test for drone-0002: MOAD-0005 Thundering Herd in cache/ttl_cache.go TTLCache.Get().
|
||||
*
|
||||
* Location: cache/ttl_cache.go TTLCache.Get()
|
||||
*
|
||||
* Defect pattern (CWE-362 cache stampede):
|
||||
* 1. fetch(key) — acquires RLock, returns cache miss
|
||||
* 2. getter.Find(ctx, key) — called with NO lock held
|
||||
* 3. mx.Lock(); cache[key] = item; mx.Unlock()
|
||||
*
|
||||
* Under concurrent load: C goroutines all see a cache miss simultaneously for
|
||||
* the same key, all call getter.Find() independently, all issue C database
|
||||
* round-trips for the same record. A thundering herd of DB queries results.
|
||||
*
|
||||
* Fix: wrap getter.Find() in a singleflight group keyed by the cache key.
|
||||
* Only the first goroutine issues the DB call; all others share its result.
|
||||
* Backend calls drop from C to 1 for any C concurrent misses on the same key.
|
||||
*
|
||||
* Compile and run (no build tool required):
|
||||
* javac defects/drone-0002/test/Drone0002Test.java -d /tmp/drone-0002
|
||||
* java -cp /tmp/drone-0002 Drone0002Test
|
||||
*/
|
||||
public class Drone0002Test {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
// --- Fake backend store with call counter ---
|
||||
|
||||
static class FakeStore {
|
||||
final AtomicInteger callCount = new AtomicInteger(0);
|
||||
final long latencyMs;
|
||||
|
||||
FakeStore(long latencyMs) { this.latencyMs = latencyMs; }
|
||||
|
||||
String find(String key) throws InterruptedException {
|
||||
callCount.incrementAndGet();
|
||||
Thread.sleep(latencyMs);
|
||||
return "value-for-" + key;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Defective cache: no singleflight ---
|
||||
|
||||
static class CacheDefective {
|
||||
final Map<String, String> cache = new ConcurrentHashMap<>();
|
||||
final FakeStore store;
|
||||
|
||||
CacheDefective(FakeStore s) { this.store = s; }
|
||||
|
||||
String get(String key) throws Exception {
|
||||
String v = cache.get(key);
|
||||
if (v != null) return v;
|
||||
// thundering herd: multiple threads all reach here simultaneously
|
||||
v = store.find(key);
|
||||
cache.put(key, v);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fixed cache: singleflight coalesces concurrent misses ---
|
||||
|
||||
static class CacheFixed {
|
||||
final Map<String, String> cache = new ConcurrentHashMap<>();
|
||||
final FakeStore store;
|
||||
final ConcurrentHashMap<String, CompletableFuture<String>> inflight = new ConcurrentHashMap<>();
|
||||
|
||||
CacheFixed(FakeStore s) { this.store = s; }
|
||||
|
||||
String get(String key) throws Exception {
|
||||
String v = cache.get(key);
|
||||
if (v != null) return v;
|
||||
|
||||
CompletableFuture<String> mine = new CompletableFuture<>();
|
||||
CompletableFuture<String> existing = inflight.putIfAbsent(key, mine);
|
||||
if (existing != null) return existing.get(); // wait for in-flight call
|
||||
|
||||
try {
|
||||
v = store.find(key);
|
||||
cache.put(key, v);
|
||||
mine.complete(v);
|
||||
return v;
|
||||
} catch (Exception e) {
|
||||
mine.completeExceptionally(e);
|
||||
throw e;
|
||||
} finally {
|
||||
inflight.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
static int countBackendCalls(boolean useFixed, int concurrent, long latencyMs)
|
||||
throws Exception {
|
||||
FakeStore store = new FakeStore(latencyMs);
|
||||
Object cache = useFixed ? new CacheFixed(store) : new CacheDefective(store);
|
||||
|
||||
CountDownLatch ready = new CountDownLatch(concurrent);
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
CountDownLatch done = new CountDownLatch(concurrent);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(concurrent);
|
||||
|
||||
for (int i = 0; i < concurrent; i++) {
|
||||
pool.submit(() -> {
|
||||
ready.countDown();
|
||||
try {
|
||||
start.await();
|
||||
if (useFixed) ((CacheFixed) cache).get("mykey");
|
||||
else ((CacheDefective) cache).get("mykey");
|
||||
} catch (Exception ignored) {
|
||||
} finally { done.countDown(); }
|
||||
});
|
||||
}
|
||||
|
||||
ready.await(5, TimeUnit.SECONDS); // all threads waiting at start gate
|
||||
start.countDown(); // release simultaneously
|
||||
done.await(10, TimeUnit.SECONDS);
|
||||
pool.shutdown();
|
||||
return store.callCount.get();
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
static void testDefectiveTriggersManyBackendCalls() throws Exception {
|
||||
int concurrent = 20;
|
||||
int calls = countBackendCalls(false, concurrent, 30);
|
||||
check("defective cache triggers >1 backend calls (thundering herd confirmed)",
|
||||
calls > 1);
|
||||
System.out.printf(" drone-0002 defective backend calls at C=%d: %d (expect ~%d)%n",
|
||||
concurrent, calls, concurrent);
|
||||
}
|
||||
|
||||
static void testFixedTriggersExactlyOneBackendCall() throws Exception {
|
||||
int concurrent = 20;
|
||||
int calls = countBackendCalls(true, concurrent, 30);
|
||||
check("fixed cache triggers exactly 1 backend call (singleflight working)",
|
||||
calls == 1);
|
||||
System.out.printf(" drone-0002 fixed backend calls at C=%d: %d (expect 1)%n",
|
||||
concurrent, calls);
|
||||
}
|
||||
|
||||
static void testFixedReturnCorrectValue() throws Exception {
|
||||
FakeStore store = new FakeStore(5);
|
||||
CacheFixed cache = new CacheFixed(store);
|
||||
String result = cache.get("mykey");
|
||||
check("fixed cache returns correct value", "value-for-mykey".equals(result));
|
||||
}
|
||||
|
||||
static void testFixedSecondCallHitsCache() throws Exception {
|
||||
FakeStore store = new FakeStore(0);
|
||||
CacheFixed cache = new CacheFixed(store);
|
||||
cache.get("k1");
|
||||
int afterFirst = store.callCount.get();
|
||||
cache.get("k1"); // second call — must hit cache
|
||||
int afterSecond = store.callCount.get();
|
||||
check("second Get on same key hits cache (no backend call)",
|
||||
afterFirst == afterSecond);
|
||||
}
|
||||
|
||||
static void testSpeedupRatioAtConcurrency20() throws Exception {
|
||||
int concurrent = 20;
|
||||
int defCalls = countBackendCalls(false, concurrent, 15);
|
||||
int fixCalls = countBackendCalls(true, concurrent, 15);
|
||||
double ratio = (double) defCalls / Math.max(1, fixCalls);
|
||||
check("fixed version reduces backend calls by at least 10x at C=20",
|
||||
ratio >= 10.0);
|
||||
System.out.printf(" drone-0002 thundering herd ratio at C=%d: %.1fx fewer backend calls%n",
|
||||
concurrent, ratio);
|
||||
}
|
||||
|
||||
// --- Harness ---
|
||||
|
||||
static void check(String desc, boolean cond) {
|
||||
if (cond) {
|
||||
System.out.println(" PASS: " + desc);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.println(" FAIL: " + desc);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("=== Drone0002Test (MOAD-0005 Thundering Herd in TTLCache.Get) ===\n");
|
||||
|
||||
testDefectiveTriggersManyBackendCalls();
|
||||
testFixedTriggersExactlyOneBackendCall();
|
||||
testFixedReturnCorrectValue();
|
||||
testFixedSecondCallHitsCache();
|
||||
testSpeedupRatioAtConcurrency20();
|
||||
|
||||
System.out.println("\n--- " + passed + " passed, " + failed + " failed ---");
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue