mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Reduced coordinate routing contention
- Published history router reads bypass the attachment monitor - Missing router reads retain synchronized attachment and detach checks - Concurrent tests cover published reads and pending lifecycle changes
This commit is contained in:
@@ -992,6 +992,10 @@ public class IrisEngine implements Engine {
|
||||
}
|
||||
|
||||
public Optional<GenerationHistoryRuntimeRouter> getGenerationHistoryRuntimeRouter() {
|
||||
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||
if (router != null) {
|
||||
return Optional.of(router);
|
||||
}
|
||||
synchronized (generationHistoryRuntimeRouterLock) {
|
||||
return Optional.ofNullable(generationHistoryRuntimeRouter);
|
||||
}
|
||||
@@ -1001,7 +1005,10 @@ public class IrisEngine implements Engine {
|
||||
int blockX,
|
||||
int blockZ
|
||||
) throws IOException {
|
||||
GenerationHistoryRuntimeRouter router;
|
||||
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||
if (router != null) {
|
||||
return router.openCoordinateScope(blockX, blockZ);
|
||||
}
|
||||
synchronized (generationHistoryRuntimeRouterLock) {
|
||||
router = generationHistoryRuntimeRouter;
|
||||
if (router == null && generationHistoryRoutingRequired) {
|
||||
|
||||
@@ -45,7 +45,10 @@ import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -554,6 +557,109 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
||||
assertThrows(IllegalStateException.class, () -> engine.getHeight(0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishedRouterLookupDoesNotWaitForTheAttachmentMonitor() throws Exception {
|
||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||
IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class));
|
||||
GenerationHistoryRuntimeRouter router = historyRouter(engine);
|
||||
engine.attachGenerationHistoryRuntimeRouter(router);
|
||||
FutureTask<Optional<GenerationHistoryRuntimeRouter>> read = new FutureTask<>(engine::getGenerationHistoryRuntimeRouter);
|
||||
Thread reader = new Thread(read, "History router lookup");
|
||||
|
||||
try {
|
||||
synchronized (engine.generationHistoryRuntimeRouterLock) {
|
||||
reader.start();
|
||||
assertSame(router, read.get(5, TimeUnit.SECONDS).orElseThrow());
|
||||
}
|
||||
} finally {
|
||||
reader.join(5_000L);
|
||||
assertTrue("Router lookup did not finish", !reader.isAlive());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishedCoordinateReadDoesNotWaitForTheAttachmentMonitor() throws Exception {
|
||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||
IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class));
|
||||
GenerationHistoryRuntimeRouter router = historyRouter(engine);
|
||||
engine.attachGenerationHistoryRuntimeRouter(router);
|
||||
GenerationHistoryRuntimeRouter.CoordinateScope scope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class);
|
||||
when(router.openCoordinateScope(-17, 32)).thenReturn(scope);
|
||||
FutureTask<GenerationHistoryRuntimeRouter.CoordinateScope> read =
|
||||
new FutureTask<>(() -> engine.openGenerationHistoryCoordinateScope(-17, 32));
|
||||
Thread reader = new Thread(read, "History coordinate read");
|
||||
|
||||
try {
|
||||
synchronized (engine.generationHistoryRuntimeRouterLock) {
|
||||
reader.start();
|
||||
assertSame(scope, read.get(5, TimeUnit.SECONDS));
|
||||
}
|
||||
} finally {
|
||||
reader.join(5_000L);
|
||||
assertTrue("Coordinate read did not finish", !reader.isAlive());
|
||||
}
|
||||
verify(router).openCoordinateScope(-17, 32);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingRouterReadWaitsForAttachmentPublication() throws Exception {
|
||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||
IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class));
|
||||
GenerationHistoryRuntimeRouter router = historyRouter(engine);
|
||||
GenerationHistoryRuntimeRouter.CoordinateScope scope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class);
|
||||
when(router.openCoordinateScope(-17, 32)).thenReturn(scope);
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
FutureTask<GenerationHistoryRuntimeRouter.CoordinateScope> read = new FutureTask<>(() -> {
|
||||
started.countDown();
|
||||
return engine.openGenerationHistoryCoordinateScope(-17, 32);
|
||||
});
|
||||
Thread reader = new Thread(read, "Pending history attachment");
|
||||
|
||||
try {
|
||||
synchronized (engine.generationHistoryRuntimeRouterLock) {
|
||||
reader.start();
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
awaitBlocked(reader);
|
||||
engine.attachGenerationHistoryRuntimeRouter(router);
|
||||
}
|
||||
assertSame(scope, read.get(5, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
reader.join(5_000L);
|
||||
assertTrue("Pending router read did not finish", !reader.isAlive());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detachedRouterReadRemainsFailClosedAcrossThreads() throws Exception {
|
||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||
IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class));
|
||||
GenerationHistoryRuntimeRouter router = historyRouter(engine);
|
||||
engine.attachGenerationHistoryRuntimeRouter(router);
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
FutureTask<GenerationHistoryRuntimeRouter.CoordinateScope> read = new FutureTask<>(() -> {
|
||||
started.countDown();
|
||||
return engine.openGenerationHistoryCoordinateScope(-17, 32);
|
||||
});
|
||||
Thread reader = new Thread(read, "Detached history read");
|
||||
|
||||
try {
|
||||
synchronized (engine.generationHistoryRuntimeRouterLock) {
|
||||
engine.detachGenerationHistoryRuntimeRouter(router);
|
||||
reader.start();
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
awaitBlocked(reader);
|
||||
}
|
||||
ExecutionException failure = assertThrows(ExecutionException.class, () -> read.get(5, TimeUnit.SECONDS));
|
||||
assertTrue(failure.getCause() instanceof IllegalStateException);
|
||||
assertEquals("Iris generation-history runtime router is detached.", failure.getCause().getMessage());
|
||||
assertTrue(engine.getGenerationHistoryRuntimeRouter().isEmpty());
|
||||
} finally {
|
||||
reader.join(5_000L);
|
||||
assertTrue("Detached router read did not finish", !reader.isAlive());
|
||||
}
|
||||
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transferredMantleIsNotClosedWithTheRetiredRuntime() throws Exception {
|
||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||
@@ -717,6 +823,14 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
||||
return router;
|
||||
}
|
||||
|
||||
private static void awaitBlocked(Thread thread) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (thread.getState() != Thread.State.BLOCKED && thread.isAlive() && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
assertEquals(Thread.State.BLOCKED, thread.getState());
|
||||
}
|
||||
|
||||
private static IrisEngine engine(
|
||||
GenerationRuntime generationRuntime,
|
||||
EngineEffects effects,
|
||||
|
||||
Reference in New Issue
Block a user