replace full locks with stamped locks

This commit is contained in:
mcrcortex
2025-05-03 13:05:21 +10:00
parent 79cd18d310
commit 86c4c8f09e
3 changed files with 43 additions and 37 deletions

View File

@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.StampedLock;
public class ActiveSectionTracker { public class ActiveSectionTracker {
@@ -18,7 +19,7 @@ public class ActiveSectionTracker {
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane //Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache; private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
private final ReentrantLock[] locks;//TODO: replace with StampedLocks private final StampedLock[] locks;
private final SectionLoader loader; private final SectionLoader loader;
private final int lruSize; private final int lruSize;
@@ -37,11 +38,11 @@ public class ActiveSectionTracker {
this.loader = loader; this.loader = loader;
this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits]; this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits];
this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap<>(cacheSize); this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap<>(cacheSize);
this.locks = new ReentrantLock[1<<numSlicesBits]; this.locks = new StampedLock[1<<numSlicesBits];
this.lruSize = cacheSize; this.lruSize = cacheSize;
for (int i = 0; i < this.loadedSectionCache.length; i++) { for (int i = 0; i < this.loadedSectionCache.length; i++) {
this.loadedSectionCache[i] = new Long2ObjectOpenHashMap<>(1024); this.loadedSectionCache[i] = new Long2ObjectOpenHashMap<>(1024);
this.locks[i] = new ReentrantLock(); this.locks[i] = new StampedLock();
} }
} }
@@ -55,26 +56,37 @@ public class ActiveSectionTracker {
final var lock = this.locks[index]; final var lock = this.locks[index];
VolatileHolder<WorldSection> holder = null; VolatileHolder<WorldSection> holder = null;
boolean isLoader = false; boolean isLoader = false;
WorldSection section; WorldSection section = null;
lock.lock();
{ {
VarHandle.fullFence(); long stamp = lock.readLock();
holder = cache.get(key); holder = cache.get(key);
if (holder == null) { if (holder != null) {//Return already loaded entry
section = holder.obj;
if (section != null) {
section.acquire();
lock.unlockRead(stamp);
return section;
}
lock.unlockRead(stamp);
} else {//Try to create holder
holder = new VolatileHolder<>(); holder = new VolatileHolder<>();
cache.put(key, holder); long ws = lock.tryConvertToWriteLock(stamp);
isLoader = true; if (ws == 0) {//Failed to convert, unlock read and get write
lock.unlockRead(stamp);
stamp = lock.writeLock();
} else {
stamp = ws;
}
var eHolder = cache.putIfAbsent(key, holder);//We put if absent because on failure to convert to write, it leaves race condition
lock.unlockWrite(stamp);
if (eHolder == null) {//We are the loader
isLoader = true;
} else {
holder = eHolder;
}
} }
section = holder.obj;
if (section != null) {
section.acquire();
lock.unlock();
return section;
}
VarHandle.fullFence();
} }
lock.unlock();
if (isLoader) { if (isLoader) {
synchronized (this.lruSecondaryCache) { synchronized (this.lruSecondaryCache) {
@@ -128,6 +140,8 @@ public class ActiveSectionTracker {
} }
} }
//lock.unlock(); //lock.unlock();
//We failed everything, try get it again
return this.acquire(key, nullOnEmpty); return this.acquire(key, nullOnEmpty);
} }
} }
@@ -137,9 +151,8 @@ public class ActiveSectionTracker {
final var cache = this.loadedSectionCache[index]; final var cache = this.loadedSectionCache[index];
WorldSection sec = null; WorldSection sec = null;
final var lock = this.locks[index]; final var lock = this.locks[index];
lock.lock(); long stamp = lock.writeLock();
{ {
VarHandle.fullFence();
if (section.trySetFreed()) { if (section.trySetFreed()) {
var cached = cache.remove(section.key); var cached = cache.remove(section.key);
var obj = cached.obj; var obj = cached.obj;
@@ -151,9 +164,8 @@ public class ActiveSectionTracker {
} }
sec = section; sec = section;
} }
VarHandle.fullFence();
} }
lock.unlock(); lock.unlockWrite(stamp);
if (sec != null) { if (sec != null) {
WorldSection a; WorldSection a;

View File

@@ -51,7 +51,7 @@ public class WorldEngine {
this.storage = storage; this.storage = storage;
this.mapper = new Mapper(this.storage); this.mapper = new Mapper(this.storage);
//5 cache size bits means that the section tracker has 32 separate maps that it uses //5 cache size bits means that the section tracker has 32 separate maps that it uses
this.sectionTracker = new ActiveSectionTracker(10, storage::loadSection, 2048, this); this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, 2048, this);
} }
public WorldSection acquireIfExists(int lvl, int x, int y, int z) { public WorldSection acquireIfExists(int lvl, int x, int y, int z) {

View File

@@ -119,10 +119,8 @@ public final class WorldSection {
public int acquire(int count) { public int acquire(int count) {
int state =((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1); int state =((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1);
if (VERIFY_WORLD_SECTION_EXECUTION) { if ((state & 1) == 0) {
if ((state & 1) == 0) { throw new IllegalStateException("Tried to acquire unloaded section");
throw new IllegalStateException("Tried to acquire unloaded section");
}
} }
return state>>1; return state>>1;
} }
@@ -134,13 +132,11 @@ public final class WorldSection {
//TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later //TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later
public int release() { public int release() {
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2; int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2;
if (VERIFY_WORLD_SECTION_EXECUTION) { if (state < 1) {
if (state < 1) { throw new IllegalStateException("Section got into an invalid state");
throw new IllegalStateException("Section got into an invalid state"); }
} if ((state & 1) == 0) {
if ((state & 1) == 0) { throw new IllegalStateException("Tried releasing a freed section");
throw new IllegalStateException("Tried releasing a freed section");
}
} }
if ((state>>1)==0) { if ((state>>1)==0) {
if (this.tracker != null) { if (this.tracker != null) {
@@ -159,10 +155,8 @@ public final class WorldSection {
//Returns true on success, false on failure //Returns true on success, false on failure
boolean trySetFreed() { boolean trySetFreed() {
int witness = (int) ATOMIC_STATE_HANDLE.compareAndExchange(this, 1, 0); int witness = (int) ATOMIC_STATE_HANDLE.compareAndExchange(this, 1, 0);
if (VERIFY_WORLD_SECTION_EXECUTION) { if ((witness & 1) == 0 && witness != 0) {
if ((witness & 1) == 0 && witness != 0) { throw new IllegalStateException("Section marked as free but has refs");
throw new IllegalStateException("Section marked as free but has refs");
}
} }
return witness == 1; return witness == 1;
} }