java-topology/defects/rocksdb/unit/RocksdbLockTxnIdsAlgorithm.java

228 lines
7.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<TransactionID> + std::find() -> O(T) per unlock
* - Fast path: unordered_set<TransactionID> -> O(1) per unlock
*
* File: utilities/transactions/lock/point/point_lock_manager.cc
* Lines: 791, 1513, 1706
*/
public class RocksdbLockTxnIdsAlgorithm {
// -----------------------------------------------------------------------
// Slow path: List<Long> + linear indexOf — mirrors autovector + std::find
// -----------------------------------------------------------------------
static class SlowLockInfo {
List<Long> 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<Long> — mirrors unordered_set<TransactionID>
// -----------------------------------------------------------------------
static class FastLockInfo {
Set<Long> 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");
}
}
}