mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Merge W2D-mantleio
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ public final class HydrologyCaveStorage {
|
||||
}
|
||||
int chunkX = x >> 4;
|
||||
int chunkZ = z >> 4;
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegions().get(Mantle.key(chunkX >> 5, chunkZ >> 5));
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegion(chunkX >> 5, chunkZ >> 5);
|
||||
if (plate == null || plate.isClosed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ public class IrisEngineMantle implements EngineMantle {
|
||||
|
||||
@Override
|
||||
public void writeSection(Matter section, java.io.DataOutputStream dos) throws IOException {
|
||||
section.writeDos(dos);
|
||||
section.writeTrimmedDos(dos);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -571,7 +571,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
}
|
||||
|
||||
private MantleChunk<Matter> existingMantleChunk(Mantle<Matter> mantle, int chunkX, int chunkZ) {
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegions().get(Mantle.key(chunkX >> 5, chunkZ >> 5));
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegion(chunkX >> 5, chunkZ >> 5);
|
||||
if (plate == null || plate.isClosed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
||||
Mantle<Matter> mantle = access.mantle();
|
||||
MantleChunk<Matter> anchor = mantle.getChunk(regionX << 5, regionZ << 5).use();
|
||||
try {
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegions().get(Mantle.key(regionX, regionZ));
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegion(regionX, regionZ);
|
||||
HashMap<Long, ObjectContinuationBundle> bundles = new HashMap<>();
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
|
||||
@@ -18,16 +18,24 @@
|
||||
|
||||
package art.arcane.iris.world.pregen;
|
||||
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Keeps the resident tectonic plate count near its cap during pregeneration. Over the cap, the least recently
|
||||
* used idle plates are saved and unloaded one at a time on the eviction executor while submissions continue;
|
||||
* a submission only waits once residency passes the cap by the eviction overshoot.
|
||||
*/
|
||||
public final class PregenMantleBackpressure {
|
||||
private static final long LOG_INTERVAL_MS = 5_000L;
|
||||
|
||||
@@ -38,6 +46,9 @@ public final class PregenMantleBackpressure {
|
||||
private final Runnable onBudgetTimeout;
|
||||
private final Supplier<String> diagnostics;
|
||||
private final BooleanSupplier cancelled;
|
||||
private final Executor evictionExecutor;
|
||||
private final AtomicBoolean evicting = new AtomicBoolean();
|
||||
private final AtomicBoolean evictionFailed = new AtomicBoolean();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final Condition progressed = lock.newCondition();
|
||||
|
||||
@@ -46,6 +57,12 @@ public final class PregenMantleBackpressure {
|
||||
}
|
||||
|
||||
public PregenMantleBackpressure(Supplier<Mantle> mantleSupplier, int maxResidentTectonicPlates, int waitMs, long timeoutMs, Runnable onBudgetTimeout, Supplier<String> diagnostics, BooleanSupplier cancelled) {
|
||||
this(mantleSupplier, maxResidentTectonicPlates, waitMs, timeoutMs, onBudgetTimeout, diagnostics, cancelled,
|
||||
command -> MultiBurst.ioBurst.execute(command));
|
||||
}
|
||||
|
||||
public PregenMantleBackpressure(Supplier<Mantle> mantleSupplier, int maxResidentTectonicPlates, int waitMs, long timeoutMs, Runnable onBudgetTimeout, Supplier<String> diagnostics, BooleanSupplier cancelled, Executor evictionExecutor) {
|
||||
this.evictionExecutor = evictionExecutor;
|
||||
this.mantleSupplier = mantleSupplier;
|
||||
this.maxResidentTectonicPlates = maxResidentTectonicPlates;
|
||||
this.waitMs = Math.max(1, waitMs);
|
||||
@@ -75,33 +92,38 @@ public final class PregenMantleBackpressure {
|
||||
return;
|
||||
}
|
||||
|
||||
evictionFailed.set(false);
|
||||
requestEviction(mantle, cap);
|
||||
int ceiling = cap + evictionOvershoot(cap);
|
||||
if (mantle.getLoadedRegionCount() <= ceiling || evictionFailed.getAndSet(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long waitStart = M.ms();
|
||||
long lastLog = 0L;
|
||||
while (mantle.getLoadedRegionCount() > cap) {
|
||||
int resident = mantle.getLoadedRegionCount();
|
||||
while (resident > ceiling) {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int freed;
|
||||
int resident;
|
||||
int before = resident;
|
||||
if (!awaitProgress() || evictionFailed.getAndSet(false)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mantle.trim(0L);
|
||||
freed = mantle.unloadTectonicPlate(0);
|
||||
requestEviction(mantle, cap);
|
||||
resident = mantle.getLoadedRegionCount();
|
||||
if (resident > cap && mantle.saveOldestIdleTectonicPlate()) {
|
||||
freed++;
|
||||
resident = mantle.getLoadedRegionCount();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
if (resident <= cap) {
|
||||
break;
|
||||
if (resident <= ceiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsed = M.ms() - waitStart;
|
||||
if (elapsed >= timeoutMs && freed == 0) {
|
||||
if (elapsed >= timeoutMs && resident >= before) {
|
||||
IrisLogging.warn("Pregen mantle backpressure exceeded " + timeoutMs + "ms with " + resident
|
||||
+ " tectonic plates resident (residency target " + cap + "); no idle plate could be evicted. "
|
||||
+ "Allowing the active dependency working set to exceed the residency target; "
|
||||
@@ -116,12 +138,42 @@ public final class PregenMantleBackpressure {
|
||||
// Pausing to stay under the plate cap is the design working, and it is reported every five
|
||||
// seconds for the whole duration of a large pregen. Only exceeding the budget is a warning.
|
||||
IrisLogging.info("Pregen mantle backpressure: " + resident + " tectonic plates resident (residency target " + cap
|
||||
+ "), freed " + freed + " last pass, waited " + elapsed + "ms.");
|
||||
+ "), waited " + elapsed + "ms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!awaitProgress()) {
|
||||
return;
|
||||
static int evictionOvershoot(int cap) {
|
||||
return Math.max(2, cap / 8);
|
||||
}
|
||||
|
||||
private void requestEviction(Mantle mantle, int cap) {
|
||||
if (!evicting.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
evictionExecutor.execute(() -> evictToCap(mantle, cap));
|
||||
} catch (Throwable e) {
|
||||
evicting.set(false);
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void evictToCap(Mantle mantle, int cap) {
|
||||
try {
|
||||
while (!mantle.isClosed() && !isCancelled() && mantle.getLoadedRegionCount() > cap
|
||||
&& mantle.saveOldestIdleTectonicPlate()) {
|
||||
signalProgress();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (!mantle.isClosed()) {
|
||||
evictionFailed.set(true);
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
} finally {
|
||||
evicting.set(false);
|
||||
signalProgress();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -74,7 +73,7 @@ public class IrisCarveBoundaryFacesTest {
|
||||
matter[0][0].slice(PreObjectMatterCell.class).set(0, 5, 0, PreObjectMatterCell.cavern(null));
|
||||
Mantle<Matter> mantle = mock(Mantle.class, RETURNS_DEEP_STUBS);
|
||||
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
|
||||
when(mantle.getLoadedRegions().get(anyLong())).thenReturn(plate);
|
||||
when(mantle.getLoadedRegion(anyInt(), anyInt())).thenReturn(plate);
|
||||
when(plate.get(31, 0)).thenReturn(chunks[1]);
|
||||
when(plate.get(1, 0)).thenReturn(chunks[2]);
|
||||
when(plate.get(0, 31)).thenReturn(chunks[3]);
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.function.Supplier;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -63,16 +64,56 @@ public class PregenMantleBackpressureTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void residencyOverTheConfiguredPlateCapIsTrimmedUntilItFits() {
|
||||
public void residencyOverTheConfiguredPlateCapEvictsLeastRecentlyUsedPlatesUntilItFits() {
|
||||
Mantle mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(5, 5, 4);
|
||||
AtomicInteger resident = new AtomicInteger(7);
|
||||
when(mantle.getLoadedRegionCount()).thenAnswer(invocation -> resident.get());
|
||||
when(mantle.saveOldestIdleTectonicPlate()).thenAnswer(invocation -> {
|
||||
resident.decrementAndGet();
|
||||
return true;
|
||||
});
|
||||
|
||||
backpressure(() -> mantle, 4, 1_000L, () -> {
|
||||
}).enforceMantleBudget();
|
||||
|
||||
verify(mantle, times(1)).trim(0L);
|
||||
verify(mantle, times(1)).unloadTectonicPlate(0);
|
||||
assertEquals(4, resident.get());
|
||||
verify(mantle, times(3)).saveOldestIdleTectonicPlate();
|
||||
verify(mantle, never()).trim(0L);
|
||||
verify(mantle, never()).unloadTectonicPlate(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void residencyWithinTheEvictionOvershootDoesNotWaitForTheEvictor() {
|
||||
Mantle mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(5);
|
||||
List<Runnable> queued = new ArrayList<>();
|
||||
AtomicInteger timeouts = new AtomicInteger();
|
||||
|
||||
new PregenMantleBackpressure(() -> mantle, 4, 600_000, 0L, timeouts::incrementAndGet, () -> "test", () -> false,
|
||||
queued::add).enforceMantleBudget();
|
||||
|
||||
assertEquals(1, queued.size());
|
||||
assertEquals(0, timeouts.get());
|
||||
verify(mantle, never()).saveOldestIdleTectonicPlate();
|
||||
queued.getFirst().run();
|
||||
verify(mantle).saveOldestIdleTectonicPlate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aSecondRequestWhileTheEvictorRunsDoesNotQueueAnotherEvictor() {
|
||||
Mantle mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(5);
|
||||
List<Runnable> queued = new ArrayList<>();
|
||||
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(() -> mantle, 4, 5, 1_000L, () -> {
|
||||
}, () -> "test", () -> false, queued::add);
|
||||
|
||||
backpressure.enforceMantleBudget();
|
||||
backpressure.enforceMantleBudget();
|
||||
|
||||
assertEquals(1, queued.size());
|
||||
queued.getFirst().run();
|
||||
backpressure.enforceMantleBudget();
|
||||
assertEquals(2, queued.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,15 +135,15 @@ public class PregenMantleBackpressureTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aTrimFailureEndsTheWaitInsteadOfSpinning() {
|
||||
public void anEvictionFailureEndsTheWaitInsteadOfSpinning() {
|
||||
Mantle mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(20);
|
||||
doThrow(new IllegalStateException("mantle is gone")).when(mantle).trim(0L);
|
||||
doThrow(new IllegalStateException("mantle is gone")).when(mantle).saveOldestIdleTectonicPlate();
|
||||
AtomicInteger timeouts = new AtomicInteger();
|
||||
|
||||
backpressure(() -> mantle, 4, 1_000L, timeouts::incrementAndGet).enforceMantleBudget();
|
||||
|
||||
verify(mantle, never()).unloadTectonicPlate(0);
|
||||
verify(mantle, times(1)).saveOldestIdleTectonicPlate();
|
||||
assertEquals(0, timeouts.get());
|
||||
}
|
||||
|
||||
@@ -115,7 +156,7 @@ public class PregenMantleBackpressureTest {
|
||||
backpressure(() -> mantle, 4, 0L, timeouts::incrementAndGet).enforceMantleBudget();
|
||||
|
||||
assertEquals(1, timeouts.get());
|
||||
verify(mantle).saveOldestIdleTectonicPlate();
|
||||
verify(mantle, atLeastOnce()).saveOldestIdleTectonicPlate();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,12 +165,11 @@ public class PregenMantleBackpressureTest {
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(20);
|
||||
AtomicInteger timeouts = new AtomicInteger();
|
||||
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
|
||||
() -> mantle, 4, 5, 0L, timeouts::incrementAndGet, () -> "cancelled", () -> true);
|
||||
() -> mantle, 4, 5, 0L, timeouts::incrementAndGet, () -> "cancelled", () -> true, Runnable::run);
|
||||
|
||||
backpressure.enforceMantleBudget();
|
||||
|
||||
verify(mantle, never()).trim(0L);
|
||||
verify(mantle, never()).unloadTectonicPlate(0);
|
||||
verify(mantle, never()).saveOldestIdleTectonicPlate();
|
||||
assertEquals(0, timeouts.get());
|
||||
}
|
||||
|
||||
@@ -141,7 +181,7 @@ public class PregenMantleBackpressureTest {
|
||||
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
|
||||
() -> mantle, 4, 5, 0L, timeouts::incrementAndGet, () -> "probing", () -> {
|
||||
throw new IllegalStateException("cancellation probe exploded");
|
||||
});
|
||||
}, Runnable::run);
|
||||
|
||||
backpressure.enforceMantleBudget();
|
||||
|
||||
@@ -154,7 +194,7 @@ public class PregenMantleBackpressureTest {
|
||||
when(mantle.getLoadedRegionCount()).thenReturn(20, 20, 20, 3);
|
||||
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
|
||||
() -> mantle, 4, 600_000, 600_000L, () -> {
|
||||
}, () -> "waiting");
|
||||
}, () -> "waiting", () -> false, Runnable::run);
|
||||
Thread waiter = new Thread(backpressure::enforceMantleBudget, "pregen-backpressure-waiter");
|
||||
|
||||
waiter.start();
|
||||
@@ -320,6 +360,6 @@ public class PregenMantleBackpressureTest {
|
||||
Runnable onBudgetTimeout
|
||||
) {
|
||||
return new PregenMantleBackpressure(
|
||||
mantleSupplier, maxResidentTectonicPlates, 5, timeoutMs, onBudgetTimeout, () -> "test");
|
||||
mantleSupplier, maxResidentTectonicPlates, 5, timeoutMs, onBudgetTimeout, () -> "test", () -> false, Runnable::run);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user