Fixed lattice eviction race

- Lattice eviction sorts a snapshot of tile access stamps
- Concurrent tile use during eviction no longer breaks the sort
- Test covers eviction under concurrent fills
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-30 22:47:20 -04:00
1 parent 6ceda7c53b
commit 618bb28591
2 files changed
+44 -10

No files matched your search

@@ -138,19 +138,19 @@ public final class HeightLattice {
return;
}
try {
List<Map.Entry<Long, Tile>> idle = new ArrayList<>(tiles.size());
List<IdleTile> idle = new ArrayList<>(tiles.size());
for (Map.Entry<Long, Tile> entry : tiles.entrySet()) {
if (entry.getValue().pending.get() == 0) {
idle.add(entry);
}
}
idle.sort(Comparator.comparingLong(entry -> entry.getValue().used));
int target = capacityTiles - (capacityTiles >> 3);
for (int index = 0; index < idle.size() && tiles.size() > target; index++) {
Map.Entry<Long, Tile> entry = idle.get(index);
Tile tile = entry.getValue();
if (tile.pending.get() == 0) {
tiles.remove(entry.getKey(), tile);
idle.add(new IdleTile(entry.getKey(), tile, tile.used));
}
}
idle.sort(Comparator.comparingLong(IdleTile::used));
int target = capacityTiles - (capacityTiles >> 3);
for (int index = 0; index < idle.size() && tiles.size() > target; index++) {
IdleTile candidate = idle.get(index);
if (candidate.tile().pending.get() == 0) {
tiles.remove(candidate.key(), candidate.tile());
}
}
} finally {
@@ -386,6 +386,9 @@ public final class HeightLattice {
int cellZ(int index);
}
private record IdleTile(Long key, Tile tile, long used) {
}
private record Span(int cellX0, int cellZ0, int width, int stride) implements Nodes {
@Override
public int cellX(int index) {
@@ -100,6 +100,37 @@ public class HeightLatticeTest {
assertEquals(DISTINCT_ACROSS_NINE, source.samples());
}
@Test
public void evictionToleratesConcurrentTileUse() throws Exception {
HeightLattice lattice = new HeightLattice(new CountingSource(), 1, false, 96 * 1024);
int threads = 12;
CyclicBarrier start = new CyclicBarrier(threads);
ExecutorService pool = Executors.newFixedThreadPool(threads);
try {
List<Future<Void>> results = new ArrayList<>();
for (int thread = 0; thread < threads; thread++) {
long seed = 31L * thread + 7L;
Callable<Void> task = () -> {
Random random = new Random(seed);
HydrologySampleBuffer out = new HydrologySampleBuffer(4);
start.await();
for (int fill = 0; fill < 40_000; fill++) {
int tileX = random.nextInt(24);
int tileZ = random.nextInt(24);
lattice.fill(tileX * 32 + random.nextInt(31), tileZ * 32 + random.nextInt(31), 2, 2, out);
}
return null;
};
results.add(pool.submit(task));
}
for (Future<Void> result : results) {
result.get(120, TimeUnit.SECONDS);
}
} finally {
pool.shutdownNow();
}
}
@Test
public void returnsBitIdenticalValues() {
HeightLattice full = new HeightLattice(new CountingSource(), 8, false, 1 << 16);