java-topology/defects/foundationdb/unit/FoundationdbTest.java

239 lines
10 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.*;
/**
* foundationdb-0001: canLaunchSrc std::count nested loop O(S × R × S')
*
* Models the data structures in DDRelocationQueue.actor.cpp:
* outer loop: relocation.src (S servers)
* inner loop: cancellableRelocations (R entries)
* innermost: std::count on servers vector (S')
*
* Compile: javac -d . FoundationdbTest.java
* Run: java unit.FoundationdbTest
*/
public class FoundationdbTest {
// Simulate a UID (storage server ID)
static class UID {
final long v;
UID(long v) { this.v = v; }
@Override public boolean equals(Object o) {
return o instanceof UID && ((UID)o).v == v;
}
@Override public int hashCode() { return Long.hashCode(v); }
@Override public String toString() { return "UID(" + v + ")"; }
}
// Simulate RelocateData.src (list of source server UIDs)
static class RelocateData {
final List<UID> src;
final int priority;
final int workFactor;
RelocateData(List<UID> src, int priority, int workFactor) {
this.src = src; this.priority = priority; this.workFactor = workFactor;
}
}
// Simulate Busyness.addWork / removeWork
static class Busyness {
int load = 0;
Busyness(int load) { this.load = load; }
Busyness copy() { return new Busyness(load); }
void removeWork(int priority, int workFactor) { load = Math.max(0, load - workFactor); }
boolean canLaunch(int priority, int workFactor) { return load + workFactor <= 100; }
}
// ---- DEFECTIVE implementation (O(S × R × S')) ----
static boolean canLaunchSrc_defective(
RelocateData relocation,
Map<UID, Busyness> busymap,
List<RelocateData> cancellableRelocations,
int[] comparisonCount) {
int workFactor = 10;
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
for (int j = 0; j < cancellableRelocations.size(); j++) { // O(R)
List<UID> servers = cancellableRelocations.get(j).src;
// std::count equivalent — O(S') linear scan
boolean found = false;
for (UID uid : servers) { // O(S')
comparisonCount[0]++;
if (uid.equals(relocation.src.get(i))) { found = true; break; }
}
if (found) {
busyCopy.removeWork(cancellableRelocations.get(j).priority,
cancellableRelocations.get(j).workFactor);
}
}
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
--neededServers;
if (neededServers == 0) return true;
}
}
return false;
}
// ---- FIXED implementation: pre-build map, O(R×S' + S) ----
static boolean canLaunchSrc_fixed(
RelocateData relocation,
Map<UID, Busyness> busymap,
List<RelocateData> cancellableRelocations,
int[] comparisonCount) {
int workFactor = 10;
int neededServers = Math.max(1, relocation.src.size() - 3 + 1);
// Build inverted index: UID -> list of cancellable relocation indices O(R×S')
Map<UID, List<Integer>> cancellableByServer = new HashMap<>();
for (int j = 0; j < cancellableRelocations.size(); j++) {
for (UID uid : cancellableRelocations.get(j).src) {
comparisonCount[0]++;
cancellableByServer.computeIfAbsent(uid, k -> new ArrayList<>()).add(j);
}
}
for (int i = 0; i < relocation.src.size(); i++) { // O(S)
Busyness busyCopy = busymap.get(relocation.src.get(i)).copy();
List<Integer> indices = cancellableByServer.get(relocation.src.get(i));
if (indices != null) {
for (int j : indices) {
busyCopy.removeWork(cancellableRelocations.get(j).priority,
cancellableRelocations.get(j).workFactor);
}
}
if (busyCopy.canLaunch(relocation.priority, workFactor)) {
--neededServers;
if (neededServers == 0) return true;
}
}
return false;
}
// Build a test scenario
static Map<UID, Busyness> buildBusymap(List<UID> allServers) {
Map<UID, Busyness> map = new HashMap<>();
for (UID uid : allServers) map.put(uid, new Busyness(50));
return map;
}
public static void main(String[] args) {
int pass = 0, fail = 0;
// -- Test 1: correctness — small scenario
{
List<UID> allServers = new ArrayList<>();
for (int i = 0; i < 5; i++) allServers.add(new UID(i));
// The relocation being considered
RelocateData candidate = new RelocateData(
Arrays.asList(allServers.get(0), allServers.get(1), allServers.get(2)),
100, 10);
// Some cancellable in-flight moves that involve the same servers
List<RelocateData> cancellable = new ArrayList<>();
cancellable.add(new RelocateData(Arrays.asList(allServers.get(0), allServers.get(3)), 50, 30));
cancellable.add(new RelocateData(Arrays.asList(allServers.get(1), allServers.get(4)), 50, 30));
Map<UID, Busyness> busymap = buildBusymap(allServers);
int[] cmpDefective = {0};
int[] cmpFixed = {0};
boolean resultDef = canLaunchSrc_defective(candidate, busymap, cancellable, cmpDefective);
boolean resultFix = canLaunchSrc_fixed(candidate, busymap, cancellable, cmpFixed);
if (resultDef == resultFix) {
System.out.printf("PASS test1: correctness — defective=%b fixed=%b%n", resultDef, resultFix);
pass++;
} else {
System.out.printf("FAIL test1: defective=%b fixed=%b%n", resultDef, resultFix);
fail++;
}
}
// -- Test 2: complexity — large R queue demonstrates O(S×R×S') vs O(R×S' + S)
// Use servers at max load so canLaunch always fails, forcing the full loop
{
int S = 3; // team size (relocation.src)
int R = 500; // queue depth (cancellable relocations)
int S2 = 3; // src team size of each cancellable relocation
// Need enough servers: 3 (candidate) + R*S2 (cancellable) = 3 + 500*3 = 1503
List<UID> servers = new ArrayList<>();
for (int i = 0; i < 3000; i++) servers.add(new UID(i));
RelocateData candidate = new RelocateData(
servers.subList(0, S), 100, 10);
// cancellable relocations — use disjoint servers so no work is removed
// and servers stay busy, forcing all S iterations to complete
List<RelocateData> cancellable = new ArrayList<>();
for (int j = 0; j < R; j++) {
int base = 100 + j * S2; // disjoint from candidate servers (0,1,2)
cancellable.add(new RelocateData(
servers.subList(base, base + S2), 50, 5));
}
// Overload the source servers so canLaunch never returns true early
Map<UID, Busyness> busymapDef = new HashMap<>();
Map<UID, Busyness> busymapFix = new HashMap<>();
for (UID uid : servers) {
busymapDef.put(uid, new Busyness(95)); // high load — can't launch
busymapFix.put(uid, new Busyness(95));
}
int[] cmpDefective = {0};
int[] cmpFixed = {0};
boolean resDef = canLaunchSrc_defective(candidate, busymapDef, cancellable, cmpDefective);
boolean resFix = canLaunchSrc_fixed(candidate, busymapFix, cancellable, cmpFixed);
System.out.printf("test2: defective comparisons=%d fixed comparisons=%d (S=%d R=%d S2=%d)%n",
cmpDefective[0], cmpFixed[0], S, R, S2);
System.out.printf("test2: defective result=%b fixed result=%b%n", resDef, resFix);
// Fixed must produce same result with fewer comparisons (no S multiplier)
// Defective: S * R * S2 = 3 * 500 * 3 = 4500 comparisons
// Fixed: R * S2 (build) = 500 * 3 = 1500 comparisons (no outer S factor)
boolean sameResult = (resDef == resFix);
boolean moreEfficient = cmpFixed[0] < cmpDefective[0];
if (sameResult && moreEfficient) {
System.out.println("PASS test2: fixed is more efficient and produces same result");
pass++;
} else {
System.out.printf("FAIL test2: sameResult=%b moreEfficient=%b%n", sameResult, moreEfficient);
fail++;
}
int expectedDefective = S * R * S2;
int expectedFixed = R * S2; // build phase dominates
System.out.printf(" Expected defective ~O(S×R×S')=%d, actual=%d%n", expectedDefective, cmpDefective[0]);
System.out.printf(" Expected fixed ~O(R×S')=%d, actual=%d%n", expectedFixed, cmpFixed[0]);
}
// -- Test 3: no cancellable relocations — both return same result
{
List<UID> servers = new ArrayList<>();
for (int i = 0; i < 3; i++) servers.add(new UID(i));
RelocateData candidate = new RelocateData(servers, 100, 5);
Map<UID, Busyness> busymap = buildBusymap(servers);
int[] c1 = {0}, c2 = {0};
boolean r1 = canLaunchSrc_defective(candidate, busymap, new ArrayList<>(), c1);
boolean r2 = canLaunchSrc_fixed(candidate, busymap, new ArrayList<>(), c2);
if (r1 == r2) {
System.out.printf("PASS test3: empty cancellable — both=%b%n", r1);
pass++;
} else {
System.out.printf("FAIL test3: mismatch on empty cancellable%n");
fail++;
}
}
System.out.printf("%nResults: %d passed, %d failed%n", pass, fail);
if (fail > 0) System.exit(1);
}
}