package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * rocksdb-001: LockInfo.txn_ids linear scan — O(T²) shared-lock churn. * * Models the RocksDB PointLockManager / PerKeyPointLockManager defect: * - Slow path: autovector + std::find() -> O(T) per unlock * - Fast path: unordered_set -> O(1) per unlock * * File: utilities/transactions/lock/point/point_lock_manager.cc * Lines: 791, 1513, 1706 */ public class RocksdbLockTxnIdsAlgorithm { // ----------------------------------------------------------------------- // Slow path: List + linear indexOf — mirrors autovector + std::find // ----------------------------------------------------------------------- static class SlowLockInfo { List txnIds = new ArrayList<>(); void addHolder(long txnId) { txnIds.add(txnId); } /** Returns number of element comparisons performed (for counting). */ long unlock(long txnId) { long ops = 0; for (int i = 0; i < txnIds.size(); i++) { ops++; if (txnIds.get(i) == txnId) { txnIds.remove(i); return ops; } } return ops; // not found } /** Shared-lock reentrant check (AcquireLocked path). */ long isReentrant(long txnId) { long ops = 0; for (Long id : txnIds) { ops++; if (id == txnId) return ops; } return ops; } } // ----------------------------------------------------------------------- // Fast path: HashSet — mirrors unordered_set // ----------------------------------------------------------------------- static class FastLockInfo { Set txnIds = new HashSet<>(); void addHolder(long txnId) { txnIds.add(txnId); } /** Returns 1 (O(1) hash lookup). */ long unlock(long txnId) { txnIds.remove(txnId); return 1L; } /** Shared-lock reentrant check — O(1). */ long isReentrant(long txnId) { return txnIds.contains(txnId) ? 1L : 1L; // always 1 op } } // ----------------------------------------------------------------------- // Result holder // ----------------------------------------------------------------------- static class Result { final String label; final long totalOps; final boolean correct; Result(String label, long totalOps, boolean correct) { this.label = label; this.totalOps = totalOps; this.correct = correct; } } // ----------------------------------------------------------------------- // Test: T concurrent readers each hold shared lock on one key. // Unlock all T — count total comparisons. // ----------------------------------------------------------------------- static Result runSlow(int T) { SlowLockInfo info = new SlowLockInfo(); for (long id = 0; id < T; id++) { info.addHolder(id); } long totalOps = 0; // Unlock each reader in reverse order (worst case for linear scan) for (long id = T - 1; id >= 0; id--) { totalOps += info.unlock(id); } // Verify all holders removed boolean correct = info.txnIds.isEmpty(); return new Result("slow", totalOps, correct); } static Result runFast(int T) { FastLockInfo info = new FastLockInfo(); for (long id = 0; id < T; id++) { info.addHolder(id); } long totalOps = 0; for (long id = T - 1; id >= 0; id--) { totalOps += info.unlock(id); } boolean correct = info.txnIds.isEmpty(); return new Result("fast", totalOps, correct); } // ----------------------------------------------------------------------- // Test: reentrant check (AcquireLocked path) with T holders // ----------------------------------------------------------------------- static Result runSlowReentrant(int T) { SlowLockInfo info = new SlowLockInfo(); for (long id = 0; id < T; id++) { info.addHolder(id); } long totalOps = 0; // Check reentrant for last-added txn (worst case: scans whole list) for (int k = 0; k < T; k++) { totalOps += info.isReentrant((long)(T - 1)); } boolean correct = true; // structural only return new Result("slow-reentrant", totalOps, correct); } static Result runFastReentrant(int T) { FastLockInfo info = new FastLockInfo(); for (long id = 0; id < T; id++) { info.addHolder(id); } long totalOps = 0; for (int k = 0; k < T; k++) { totalOps += info.isReentrant((long)(T - 1)); } boolean correct = true; return new Result("fast-reentrant", totalOps, correct); } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { int passed = 0; int total = 0; int[] sizes = {10, 100, 500, 1000}; for (int T : sizes) { // --- Unlock path --- Result slow = runSlow(T); Result fast = runFast(T); // Slow should be O(T^2/2) comparisons (average T/2 per unlock, T unlocks) long expectedSlowMin = (long) T * (T / 4); // lower bound long expectedSlowMax = (long) T * T; // upper bound long expectedFast = (long) T; // exactly T unlocks × 1 op each boolean slowOk = slow.correct && slow.totalOps >= expectedSlowMin && slow.totalOps <= expectedSlowMax; boolean fastOk = fast.correct && fast.totalOps == expectedFast; System.out.printf( "UNLOCK T=%4d slow_ops=%6d fast_ops=%4d speedup=%.1fx slow=%s fast=%s%n", T, slow.totalOps, fast.totalOps, (double) slow.totalOps / fast.totalOps, slowOk ? "PASS" : "FAIL", fastOk ? "PASS" : "FAIL" ); total += 2; if (slowOk) passed++; if (fastOk) passed++; // --- Reentrant path --- Result slowR = runSlowReentrant(T); Result fastR = runFastReentrant(T); long expectedSlowRMin = (long) T * (T / 4); long expectedSlowRMax = (long) T * T; long expectedFastR = (long) T; boolean slowROk = slowR.correct && slowR.totalOps >= expectedSlowRMin && slowR.totalOps <= expectedSlowRMax; boolean fastROk = fastR.correct && fastR.totalOps == expectedFastR; System.out.printf( "REENT T=%4d slow_ops=%6d fast_ops=%4d speedup=%.1fx slow=%s fast=%s%n", T, slowR.totalOps, fastR.totalOps, (double) slowR.totalOps / fastR.totalOps, slowROk ? "PASS" : "FAIL", fastROk ? "PASS" : "FAIL" ); total += 2; if (slowROk) passed++; if (fastROk) passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); if (passed != total) { throw new AssertionError("Some tests FAILED"); } } }