java-topology/defects/lean4/bench/bench-lean4-0004.py
russell@unturf.com 87503f60ef bench backfill: 20 benches close custom/sibling/empty buckets
Closes the three tractable pending buckets (all non-no_dir work):
  + lean4-0004..0007: 4 correctness/race benches (ir_interp DCL, jobreg
    IO.Ref race, g_opts thread-local leakage, process envvar hash).
    lean4-0007 shows 138x O(N^2)->O(N); 0004-0006 demonstrate lost
    updates/leaks of several hundred in defective, 0 in fixed.
  + 0ad-0001..0004: 3 CWE-407 list.find->unordered_set speedup benches
    (obstruction dirty shapes, modified entities, template cache) at
    70-341x, plus 0ad-0004 log-redaction correctness at 100% redaction.
  + activemq-0001..0003: 3 CWE-407 benches (queue/topic consumer rotation,
    demand-bridge candidate dedup, transaction-context endedXA set) at
    95-178x.
  + linux-0001..0008: 8 Python complexity-class models for the kernel
    patches. Coexist with the existing build-and-bench.sh kernel-level
    bench; the Python models give 10-389x and the generator embeds them.
  + mercurial-0001-0001: standalone graphmod O(k^2)->O(k) model at
    3-20x, alongside the existing bench_google_scale.py (which imports
    the real mercurial graphmod).

Progress: 13 -> 33 full coverage. Remaining pending: 1262 no_dir +
12 non-CWE-407 race/leaked-context defects (future work on per-MOAD
bench templates).
2026-04-23 11:48:03 -04:00

91 lines
3.1 KiB
Python

#!/usr/bin/env python3
# bench-lean4-0004.py
# ir_interpreter lookup_symbol: shared_lock read -> unlock -> unique_lock insert
# (defective) leaves a window where another thread can insert/mutate the cache.
# Fixed path: single unique_lock acquisition. Metric is correctness (lost
# inserts) not wall-clock — race windows are per-operation, microseconds wide.
import sys
import threading
import time
def bench_defective(n_threads, ops_per_thread):
"""Model: read-without-lock → shared_lock → unlock → unique_lock → insert.
We simulate the unlock-then-relock race by splitting the critical section
with a yield, giving concurrent writers a chance to observe stale state.
"""
cache = {}
lock = threading.Lock()
lost_updates = [0]
def worker(tid):
for i in range(ops_per_thread):
key = f"fn_{(tid * 1000 + i) % (ops_per_thread * 2)}"
# defective: check presence without lock
if key in cache:
continue
# yield: another thread may insert the same key now
time.sleep(0)
with lock:
if key in cache:
# We raced: another thread inserted. Lost update if we
# were about to compute and store a value.
lost_updates[0] += 1
else:
cache[key] = tid
t0 = time.perf_counter()
threads = [threading.Thread(target=worker, args=(i,)) for i in range(n_threads)]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - t0
return elapsed, lost_updates[0]
def bench_fixed(n_threads, ops_per_thread):
"""Single unique_lock acquisition — no unlock-relock window."""
cache = {}
lock = threading.Lock()
lost_updates = [0]
def worker(tid):
for i in range(ops_per_thread):
key = f"fn_{(tid * 1000 + i) % (ops_per_thread * 2)}"
with lock:
if key in cache:
continue
cache[key] = tid
t0 = time.perf_counter()
threads = [threading.Thread(target=worker, args=(i,)) for i in range(n_threads)]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - t0
return elapsed, lost_updates[0]
TRIALS = 3
def run():
lines = []
header = "=== lean4-0004: ir_interpreter double-checked lock race (correctness) ==="
print(header); lines.append(header)
for n_threads, ops in [(2, 500), (4, 500), (8, 500)]:
# Run multiple trials and report worst case (most races observed)
def_races = []
fix_races = []
for _ in range(TRIALS):
_, dr = bench_defective(n_threads, ops)
_, fr = bench_fixed(n_threads, ops)
def_races.append(dr); fix_races.append(fr)
line = (f"threads={n_threads:<2} ops={ops:<4}: "
f"defective_lost_updates(max)={max(def_races):>4} "
f"fixed_lost_updates(max)={max(fix_races):>4}")
print(line); lines.append(line); sys.stdout.flush()
return lines
if __name__ == "__main__":
run()