replace full locks with stamped locks
This commit is contained in:
@@ -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
|
||||||
holder = new VolatileHolder<>();
|
|
||||||
cache.put(key, holder);
|
|
||||||
isLoader = true;
|
|
||||||
}
|
|
||||||
section = holder.obj;
|
section = holder.obj;
|
||||||
if (section != null) {
|
if (section != null) {
|
||||||
section.acquire();
|
section.acquire();
|
||||||
lock.unlock();
|
lock.unlockRead(stamp);
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
VarHandle.fullFence();
|
lock.unlockRead(stamp);
|
||||||
|
} else {//Try to create holder
|
||||||
|
holder = new VolatileHolder<>();
|
||||||
|
long ws = lock.tryConvertToWriteLock(stamp);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -119,11 +119,9 @@ 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,14 +132,12 @@ 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) {
|
||||||
this.tracker.tryUnload(this);
|
this.tracker.tryUnload(this);
|
||||||
@@ -159,11 +155,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user