mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +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:
1 parent
bfaacb4d38
commit
eb43cc44fa
2 files changed
+122
-1
No files matched your search
@@ -992,6 +992,10 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<GenerationHistoryRuntimeRouter> getGenerationHistoryRuntimeRouter() {
|
public Optional<GenerationHistoryRuntimeRouter> getGenerationHistoryRuntimeRouter() {
|
||||||
|
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||||
|
if (router != null) {
|
||||||
|
return Optional.of(router);
|
||||||
|
}
|
||||||
synchronized (generationHistoryRuntimeRouterLock) {
|
synchronized (generationHistoryRuntimeRouterLock) {
|
||||||
return Optional.ofNullable(generationHistoryRuntimeRouter);
|
return Optional.ofNullable(generationHistoryRuntimeRouter);
|
||||||
}
|
}
|
||||||
@@ -1001,7 +1005,10 @@ public class IrisEngine implements Engine {
|
|||||||
int blockX,
|
int blockX,
|
||||||
int blockZ
|
int blockZ
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
GenerationHistoryRuntimeRouter router;
|
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||||
|
if (router != null) {
|
||||||
|
return router.openCoordinateScope(blockX, blockZ);
|
||||||
|
}
|
||||||
synchronized (generationHistoryRuntimeRouterLock) {
|
synchronized (generationHistoryRuntimeRouterLock) {
|
||||||
router = generationHistoryRuntimeRouter;
|
router = generationHistoryRuntimeRouter;
|
||||||
if (router == null && generationHistoryRoutingRequired) {
|
if (router == null && generationHistoryRoutingRequired) {
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ import java.util.IdentityHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
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.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -554,6 +557,109 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
assertThrows(IllegalStateException.class, () -> engine.getHeight(0, 0));
|
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
|
@Test
|
||||||
public void transferredMantleIsNotClosedWithTheRetiredRuntime() throws Exception {
|
public void transferredMantleIsNotClosedWithTheRetiredRuntime() throws Exception {
|
||||||
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
RuntimeFixture active = runtime(1, 1D, 1D, 1D);
|
||||||
@@ -717,6 +823,14 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
return router;
|
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(
|
private static IrisEngine engine(
|
||||||
GenerationRuntime generationRuntime,
|
GenerationRuntime generationRuntime,
|
||||||
EngineEffects effects,
|
EngineEffects effects,
|
||||||
|
|||||||
Reference in new issue
Block a user