mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Poll instead of sleeping
- Add the Await test helper that polls a condition every ten milliseconds under a bounded timeout - Replace every Thread.sleep in the fourteen polling test classes with a condition wait, a latch wait or a bounded quiet window - Keep the crash-consistency fsync interception in GenerationHistoryAdmissionTest and SavedBiomeStoreBatchTest on real barriers
This commit is contained in:
1 parent
c75b0e01af
commit
42798c6340
15 files changed
+174
-193
No files matched your search
@@ -2,8 +2,10 @@ package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.volmlib.util.hotload.ConfigHotloadEngine;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import java.time.Duration;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -213,12 +215,12 @@ public class SettingsHotloadWatchTest {
|
||||
assertEquals(1, automaticApplies.get());
|
||||
|
||||
Files.writeString(override.toPath(), locale("en_US", "Manual {permission}"), StandardCharsets.UTF_8);
|
||||
pollFor(125L);
|
||||
pollWithoutChange(125L, "First " + PERMISSION);
|
||||
assertEquals("First " + PERMISSION, permissionMessage());
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "en_US"));
|
||||
assertEquals("Manual " + PERMISSION, permissionMessage());
|
||||
|
||||
pollFor(650L);
|
||||
pollWithoutChange(650L, "Manual " + PERMISSION);
|
||||
assertEquals(1, automaticApplies.get());
|
||||
assertEquals("Manual " + PERMISSION, permissionMessage());
|
||||
}
|
||||
@@ -254,15 +256,11 @@ public class SettingsHotloadWatchTest {
|
||||
assertEquals("Active " + PERMISSION, permissionMessage());
|
||||
}
|
||||
|
||||
private void awaitPermissionMessage(String expected) throws Exception {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(6L);
|
||||
while (System.nanoTime() < deadline) {
|
||||
private void awaitPermissionMessage(String expected) {
|
||||
Await.reached("the hotload permission message '" + expected + "'", Duration.ofSeconds(6L), () -> {
|
||||
watch.checkConfigHotload();
|
||||
if (expected.equals(permissionMessage())) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(25L);
|
||||
}
|
||||
return expected.equals(permissionMessage());
|
||||
});
|
||||
assertEquals(expected, permissionMessage());
|
||||
}
|
||||
|
||||
@@ -275,31 +273,24 @@ public class SettingsHotloadWatchTest {
|
||||
}
|
||||
|
||||
private void checkUntilEntered(CountDownLatch entered) {
|
||||
try {
|
||||
while (entered.getCount() > 0L) {
|
||||
watch.checkConfigHotload();
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
} catch (InterruptedException failure) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("Interrupted while waiting for a hotload snapshot", failure);
|
||||
}
|
||||
Await.until("the hotload apply boundary", Duration.ofSeconds(5L), () -> {
|
||||
watch.checkConfigHotload();
|
||||
return entered.getCount() == 0L;
|
||||
});
|
||||
}
|
||||
|
||||
private void awaitThreadState(Thread thread, Thread.State expected) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(1L);
|
||||
while (System.nanoTime() < deadline && thread.getState() != expected) {
|
||||
Thread.sleep(5L);
|
||||
}
|
||||
private void awaitThreadState(Thread thread, Thread.State expected) {
|
||||
Await.reached(thread.getName() + " to reach " + expected, Duration.ofSeconds(1L),
|
||||
() -> thread.getState() == expected);
|
||||
assertEquals(expected, thread.getState());
|
||||
}
|
||||
|
||||
private void pollFor(long durationMillis) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(durationMillis);
|
||||
while (System.nanoTime() < deadline) {
|
||||
watch.checkConfigHotload();
|
||||
Thread.sleep(25L);
|
||||
}
|
||||
private void pollWithoutChange(long windowMillis, String expected) {
|
||||
assertFalse("The hotload permission message changed inside the quiet window",
|
||||
Await.reached("an unexpected hotload apply", Duration.ofMillis(windowMillis), () -> {
|
||||
watch.checkConfigHotload();
|
||||
return !expected.equals(permissionMessage());
|
||||
}));
|
||||
}
|
||||
|
||||
private String captureErrorsWhilePolling(String expected, long duplicateWindowMillis) throws Exception {
|
||||
@@ -307,20 +298,17 @@ public class SettingsHotloadWatchTest {
|
||||
ByteArrayOutputStream captured = new ByteArrayOutputStream();
|
||||
try (PrintStream capture = new PrintStream(captured, true, StandardCharsets.UTF_8)) {
|
||||
System.setErr(capture);
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(4L);
|
||||
while (!captured.toString(StandardCharsets.UTF_8).contains(expected)) {
|
||||
if (System.nanoTime() >= deadline) {
|
||||
fail("Timed out after 4000ms waiting for the hotload diagnostic '" + expected
|
||||
+ "'; captured stderr was: " + captured.toString(StandardCharsets.UTF_8));
|
||||
}
|
||||
if (!Await.reached("the hotload diagnostic '" + expected + "'", Duration.ofSeconds(4L), () -> {
|
||||
watch.checkConfigHotload();
|
||||
Thread.sleep(10L);
|
||||
return captured.toString(StandardCharsets.UTF_8).contains(expected);
|
||||
})) {
|
||||
fail("Timed out after 4000ms waiting for the hotload diagnostic '" + expected
|
||||
+ "'; captured stderr was: " + captured.toString(StandardCharsets.UTF_8));
|
||||
}
|
||||
long duplicateDeadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(duplicateWindowMillis);
|
||||
while (System.nanoTime() < duplicateDeadline) {
|
||||
Await.reached("a duplicate hotload diagnostic", Duration.ofMillis(duplicateWindowMillis), () -> {
|
||||
watch.checkConfigHotload();
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} finally {
|
||||
System.setErr(originalError);
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ public final class NoiseRenderCoordinatorTest {
|
||||
AtomicReference<Throwable> failure = new AtomicReference<>();
|
||||
AtomicReference<String> samplingThread = new AtomicReference<>();
|
||||
AtomicBoolean sampledOnEdt = new AtomicBoolean();
|
||||
AtomicBoolean renderFinished = new AtomicBoolean();
|
||||
CountDownLatch sampledAfterCompletion = new CountDownLatch(1);
|
||||
AtomicInteger samples = new AtomicInteger();
|
||||
NoiseRenderCoordinator coordinator = new NoiseRenderCoordinator(2, new NoiseRenderCoordinator.Listener() {
|
||||
@Override
|
||||
@@ -154,6 +156,9 @@ public final class NoiseRenderCoordinatorTest {
|
||||
try {
|
||||
NoiseProvider sampler = (x, z) -> {
|
||||
samples.incrementAndGet();
|
||||
if (renderFinished.get()) {
|
||||
sampledAfterCompletion.countDown();
|
||||
}
|
||||
samplingThread.compareAndSet(null, Thread.currentThread().getName());
|
||||
sampledOnEdt.compareAndSet(false, SwingUtilities.isEventDispatchThread());
|
||||
return x + z;
|
||||
@@ -177,7 +182,8 @@ public final class NoiseRenderCoordinatorTest {
|
||||
assertFalse(sampledOnEdt.get());
|
||||
assertTrue(samplingThread.get().startsWith("Iris Noise Renderer"));
|
||||
int completedSamples = samples.get();
|
||||
Thread.sleep(150L);
|
||||
renderFinished.set(true);
|
||||
assertFalse(sampledAfterCompletion.await(150L, TimeUnit.MILLISECONDS));
|
||||
assertEquals(completedSamples, samples.get());
|
||||
} finally {
|
||||
coordinator.close();
|
||||
|
||||
@@ -2,9 +2,11 @@ package art.arcane.iris.core.gui;
|
||||
|
||||
import art.arcane.iris.engine.framework.render.IrisRenderer;
|
||||
import art.arcane.iris.engine.framework.render.RenderType;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -86,7 +88,8 @@ public class VisionRenderControllerTest {
|
||||
VisionRenderController.RenderSpec spec = spec(renderer, RenderType.BIOME, 1L, 0D, 0D, 4D, 128, 128);
|
||||
try {
|
||||
VisionRenderController.Frame first = controller.request(spec);
|
||||
assertTrue(await(() -> controller.progress(first).ready() == controller.progress(first).total(), 2_000L));
|
||||
Await.until("the first render to report every tile ready", Duration.ofSeconds(2L),
|
||||
() -> controller.progress(first).ready() == controller.progress(first).total());
|
||||
int rendered = calls.get();
|
||||
|
||||
VisionRenderController.Frame cached = controller.request(spec);
|
||||
@@ -108,7 +111,8 @@ public class VisionRenderControllerTest {
|
||||
VisionRenderController controller = controller(2);
|
||||
try {
|
||||
VisionRenderController.Frame first = controller.request(spec(renderer, RenderType.BIOME, 1L, 16D, 16D, 4D, 128, 128));
|
||||
assertTrue(await(() -> controller.progress(first).ready() == controller.progress(first).total(), 2_000L));
|
||||
Await.until("the first render to report every tile ready", Duration.ofSeconds(2L),
|
||||
() -> controller.progress(first).ready() == controller.progress(first).total());
|
||||
Set<VisionRenderController.TileKey> firstKeys = keys(first);
|
||||
|
||||
VisionRenderController.Frame panned = controller.request(spec(renderer, RenderType.BIOME, 1L, 20D, 20D, 4D, 128, 128));
|
||||
@@ -249,15 +253,4 @@ public class VisionRenderControllerTest {
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean await(BooleanSupplier condition, long timeoutMillis) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMillis);
|
||||
while (System.nanoTime() < deadline) {
|
||||
if (condition.getAsBoolean()) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(5L);
|
||||
}
|
||||
return condition.getAsBoolean();
|
||||
}
|
||||
}
|
||||
@@ -61,11 +61,12 @@ public class PackDownloadExecutionTest {
|
||||
public void cancellationInterruptsRunningWorkOutsidePublication() throws Exception {
|
||||
LifecycleOperationCoordinator.Lease lease = mock(LifecycleOperationCoordinator.Lease.class);
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch neverReleased = new CountDownLatch(1);
|
||||
AtomicBoolean interrupted = new AtomicBoolean();
|
||||
PackDownloadExecution execution = new PackDownloadExecution(lease, cancellation -> {
|
||||
started.countDown();
|
||||
try {
|
||||
Thread.sleep(TimeUnit.SECONDS.toMillis(30L));
|
||||
neverReleased.await(30L, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
interrupted.set(true);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
+9
-27
@@ -7,6 +7,8 @@ import art.arcane.iris.engine.framework.BiomeEnvironment;
|
||||
import art.arcane.iris.engine.history.SavedBiomeRuntime;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.testsupport.PlatformBinding;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import art.arcane.iris.engine.framework.EngineMode;
|
||||
@@ -27,23 +29,19 @@ import art.arcane.iris.engine.object.IrisDimensionCarvingEntry;
|
||||
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.engine.object.IrisRange;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.context.IrisContext;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
@@ -65,7 +63,6 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
@@ -78,21 +75,8 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisEngineGenerationRuntimeScopeTest {
|
||||
@BeforeClass
|
||||
public static void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.block(anyString())).thenReturn(block);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
@ClassRule
|
||||
public static final PlatformBinding PLATFORM = PlatformBinding.mockPlatform();
|
||||
|
||||
@Test
|
||||
public void carvingResolverRestoresNestedRuntimeDefinitions() throws Exception {
|
||||
@@ -878,11 +862,9 @@ 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);
|
||||
}
|
||||
private static void awaitBlocked(Thread thread) {
|
||||
Await.reached("the reader to block on the engine lifecycle lock", Duration.ofSeconds(5L),
|
||||
() -> thread.getState() == Thread.State.BLOCKED || !thread.isAlive());
|
||||
assertEquals(Thread.State.BLOCKED, thread.getState());
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@ import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter.SavedChunkM
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
import art.arcane.iris.engine.platform.EngineBukkitOps;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.testsupport.PlatformBinding;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
@@ -17,13 +15,13 @@ import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -52,26 +50,9 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class WorldChunkMaintenanceRoutingTest {
|
||||
private static IrisPlatform previousPlatform;
|
||||
@ClassRule
|
||||
public static final PlatformBinding PLATFORM = PlatformBinding.mockPlatform();
|
||||
|
||||
@BeforeClass
|
||||
public static void bindPlatform() {
|
||||
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
|
||||
IrisPlatforms.unbind();
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
when(registries.block(anyString())).thenReturn(mock(PlatformBlockState.class));
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void restorePlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
if (previousPlatform != null) {
|
||||
IrisPlatforms.bind(previousPlatform);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void materializesTheSavedOwnerAndKeepsSpawningAfterCompletion() throws Exception {
|
||||
@@ -372,10 +353,7 @@ public class WorldChunkMaintenanceRoutingTest {
|
||||
|
||||
private void awaitRelease() throws InterruptedException {
|
||||
assertTrue("Saved mantle handle was not released", released.tryAcquire(5L, TimeUnit.SECONDS));
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (!pending.isEmpty() && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
Await.reached("the saved mantle cleanup to finish", Duration.ofSeconds(5L), pending::isEmpty);
|
||||
assertTrue("Saved mantle cleanup did not finish", pending.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package art.arcane.iris.engine.history;
|
||||
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -14,6 +16,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -38,7 +41,7 @@ public class GenerationAdmissionTest {
|
||||
});
|
||||
|
||||
assertTrue(requested.await(5L, TimeUnit.SECONDS));
|
||||
Thread.sleep(100L);
|
||||
assertThrows(TimeoutException.class, () -> cutover.get(100L, TimeUnit.MILLISECONDS));
|
||||
assertFalse(cutover.isDone());
|
||||
stage.close();
|
||||
cutover.get(5L, TimeUnit.SECONDS);
|
||||
@@ -246,12 +249,14 @@ public class GenerationAdmissionTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitWaiting(Thread thread) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (thread.getState() != Thread.State.WAITING && System.nanoTime() < deadline) {
|
||||
private static void awaitWaiting(Thread thread) {
|
||||
Await.reached("the generation operation to reach the admission gate", Duration.ofSeconds(5L), () -> {
|
||||
if (thread.getState() == Thread.State.WAITING) {
|
||||
return true;
|
||||
}
|
||||
assertTrue("Generation operation completed before reaching the admission gate.", thread.isAlive());
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
assertEquals("Generation operation did not reach the admission gate.", Thread.State.WAITING, thread.getState());
|
||||
}
|
||||
|
||||
|
||||
+17
-20
@@ -1,5 +1,6 @@
|
||||
package art.arcane.iris.engine.history;
|
||||
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -13,6 +14,7 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -251,10 +253,8 @@ public class GenerationHistoryAdmissionTest {
|
||||
Field queueField = GenerationHistory.class.getDeclaredField("pendingSemanticClaims");
|
||||
queueField.setAccessible(true);
|
||||
ArrayBlockingQueue<?> queue = (ArrayBlockingQueue<?>) queueField.get(history);
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (queue.remainingCapacity() != 0 && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
Await.reached("the claim queue to reach capacity", Duration.ofSeconds(5L),
|
||||
() -> queue.remainingCapacity() == 0);
|
||||
assertEquals(0, queue.remainingCapacity());
|
||||
assertEquals(128, queue.size());
|
||||
}
|
||||
@@ -354,24 +354,21 @@ public class GenerationHistoryAdmissionTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitBlockedOnHistory(GenerationHistory history, List<Thread> workers, int count)
|
||||
throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (System.nanoTime() < deadline) {
|
||||
int blocked = 0;
|
||||
for (Thread worker : workers) {
|
||||
ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(worker.threadId());
|
||||
if (info != null && info.getThreadState() == Thread.State.BLOCKED
|
||||
&& info.getLockInfo().getIdentityHashCode() == System.identityHashCode(history)) {
|
||||
blocked++;
|
||||
}
|
||||
private static void awaitBlockedOnHistory(GenerationHistory history, List<Thread> workers, int count) {
|
||||
Await.until("semantic claim callers to queue behind the History monitor", Duration.ofSeconds(5L),
|
||||
() -> blockedOnHistory(history, workers) == count);
|
||||
}
|
||||
|
||||
private static int blockedOnHistory(GenerationHistory history, List<Thread> workers) {
|
||||
int blocked = 0;
|
||||
for (Thread worker : workers) {
|
||||
ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(worker.threadId());
|
||||
if (info != null && info.getThreadState() == Thread.State.BLOCKED
|
||||
&& info.getLockInfo().getIdentityHashCode() == System.identityHashCode(history)) {
|
||||
blocked++;
|
||||
}
|
||||
if (blocked == count) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
throw new AssertionError("Semantic claim callers did not queue behind the History monitor");
|
||||
return blocked;
|
||||
}
|
||||
|
||||
private static boolean persistPaused(GenerationHistory history, GenerationHistory.GenerationStage stage,
|
||||
|
||||
@@ -5,8 +5,11 @@ import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.framework.BiomeEnvironment;
|
||||
import art.arcane.iris.engine.framework.PreservationRegistry;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -15,6 +18,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -39,6 +43,9 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class SavedBiomeRuntimeTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@@ -263,10 +270,8 @@ public class SavedBiomeRuntimeTest {
|
||||
consumption.writeLock().lock();
|
||||
Future<Boolean> rendered = caller.submit(() -> runtime.readSurfaceBiome(0, 0, Optional::isEmpty));
|
||||
try {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (runtime.cachedQueryCount() == 0 && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
Await.reached("the background preview to cache its query", Duration.ofSeconds(5L),
|
||||
() -> runtime.cachedQueryCount() != 0);
|
||||
assertEquals(1, runtime.cachedQueryCount());
|
||||
for (int chunkX = 1; chunkX <= 130; chunkX++) {
|
||||
int requestedX = chunkX;
|
||||
@@ -451,11 +456,9 @@ public class SavedBiomeRuntimeTest {
|
||||
when(history.semantics(anyInt(), anyInt())).thenReturn(Optional.empty());
|
||||
}
|
||||
|
||||
private static void awaitIdle(SavedBiomeRuntime runtime) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (runtime.pendingQueryCount() != 0 && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
private static void awaitIdle(SavedBiomeRuntime runtime) {
|
||||
Await.reached("the runtime to drain its pending queries", Duration.ofSeconds(5L),
|
||||
() -> runtime.pendingQueryCount() == 0);
|
||||
assertEquals(0, runtime.pendingQueryCount());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package art.arcane.iris.engine.history;
|
||||
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -11,6 +12,7 @@ import java.lang.reflect.Field;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
@@ -207,10 +209,8 @@ public class SavedBiomeStoreBatchTest {
|
||||
for (SavedBiomeChunk chunk : chunks) {
|
||||
results.add(workers.submit(() -> claim(store, root, control, chunk)));
|
||||
}
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (pending.size() != chunks.size() && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
Await.reached("every claim to queue behind the region write", Duration.ofSeconds(5L),
|
||||
() -> pending.size() == chunks.size());
|
||||
assertEquals(chunks.size(), pending.size());
|
||||
} finally {
|
||||
writingField.setBoolean(stripe, false);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package art.arcane.iris.engine.hydrology;
|
||||
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.volmlib.util.cache.CacheKey;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -137,10 +139,7 @@ public class HydrologyTileCacheTest {
|
||||
Field field = HydrologyTileCache.class.getDeclaredField("closed");
|
||||
field.setAccessible(true);
|
||||
AtomicBoolean closed = (AtomicBoolean) field.get(cache);
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (!closed.get() && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Await.reached("the cache to finish closing", Duration.ofSeconds(5L), closed::get);
|
||||
assertTrue(closed.get());
|
||||
}
|
||||
|
||||
@@ -1162,22 +1161,13 @@ public class HydrologyTileCacheTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitConcurrentPlan(AtomicInteger inFlight, long boundMillis) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(boundMillis);
|
||||
while (inFlight.get() < 2 && System.nanoTime() < deadline) {
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
private static void awaitConcurrentPlan(AtomicInteger inFlight, long boundMillis) {
|
||||
Await.reached("a second plan to run concurrently", Duration.ofMillis(boundMillis), () -> inFlight.get() >= 2);
|
||||
}
|
||||
|
||||
private static void awaitParkedCallers(List<Thread> callers, int expected) throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (System.nanoTime() < deadline) {
|
||||
if (callers.size() == expected && allParked(callers)) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(1L);
|
||||
}
|
||||
throw new AssertionError("Callers did not park on the in-flight plan");
|
||||
private static void awaitParkedCallers(List<Thread> callers, int expected) {
|
||||
Await.until("callers to park on the in-flight plan", Duration.ofSeconds(5L),
|
||||
() -> callers.size() == expected && allParked(callers));
|
||||
}
|
||||
|
||||
private static boolean allParked(List<Thread> callers) {
|
||||
|
||||
+9
-17
@@ -3,10 +3,8 @@ package art.arcane.iris.engine.mantle;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.testsupport.PlatformBinding;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.context.IrisContext;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
@@ -16,9 +14,11 @@ import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -43,7 +43,6 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -52,24 +51,19 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MatterGeneratorConcurrencyTest {
|
||||
@ClassRule
|
||||
public static final PlatformBinding PLATFORM = PlatformBinding.mockPlatform();
|
||||
|
||||
private static IrisSettings previousSettings;
|
||||
|
||||
@BeforeClass
|
||||
public static void bindPlatform() {
|
||||
previousSettings = IrisSettings.settings;
|
||||
IrisSettings.settings = new IrisSettings();
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBlockState defaultBlock = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(registries.block(anyString())).thenReturn(defaultBlock);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
IrisSettings.settings = previousSettings;
|
||||
}
|
||||
|
||||
@@ -592,10 +586,8 @@ public class MatterGeneratorConcurrencyTest {
|
||||
return null;
|
||||
});
|
||||
|
||||
long deadline = System.currentTimeMillis() + 10_000L;
|
||||
while (secondCompleted.size() < 7 && System.currentTimeMillis() < deadline) {
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
Await.reached("the second generation to finish its own seven chunks", Duration.ofSeconds(10L),
|
||||
() -> secondCompleted.size() >= 7);
|
||||
// The second generation's window is x 1..3, z -1..1. The first generation finished (1,-1)
|
||||
// before parking on (1,0), so the second one owns the remaining seven chunks and must
|
||||
// complete all of them while (1,0) is still held instead of blocking on it first.
|
||||
|
||||
+10
-6
@@ -4,6 +4,7 @@ import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.iris.testsupport.BukkitTestServer;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -14,6 +15,7 @@ import org.mockito.invocation.InvocationOnMock;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -22,6 +24,7 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -281,17 +284,18 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
new WeakReference<>(fixture.engine()));
|
||||
}
|
||||
|
||||
private static void awaitCollection(LifetimeReferences references) throws InterruptedException {
|
||||
for (int attempt = 0; attempt < 100; attempt++) {
|
||||
private static void awaitCollection(LifetimeReferences references) {
|
||||
AtomicInteger attempt = new AtomicInteger();
|
||||
Await.reached("the thread-local State and Engine to be collected", Duration.ofSeconds(5L), () -> {
|
||||
System.gc();
|
||||
if (references.state().get() == null
|
||||
&& references.engine().get() == null) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
byte[] pressure = new byte[1_048_576];
|
||||
pressure[0] = (byte) attempt;
|
||||
Thread.sleep(10L);
|
||||
}
|
||||
pressure[0] = (byte) attempt.incrementAndGet();
|
||||
return false;
|
||||
});
|
||||
assertTrue("Thread-local State was retained", references.state().get() == null);
|
||||
assertTrue("Engine was retained", references.engine().get() == null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package art.arcane.iris.testsupport;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public final class Await {
|
||||
private static final long POLL_INTERVAL_NANOS = TimeUnit.MILLISECONDS.toNanos(10L);
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5L);
|
||||
|
||||
private Await() {
|
||||
}
|
||||
|
||||
public static void until(String what, BooleanSupplier condition) {
|
||||
until(what, DEFAULT_TIMEOUT, condition);
|
||||
}
|
||||
|
||||
public static void until(String what, Duration timeout, BooleanSupplier condition) {
|
||||
if (!reached(what, timeout, condition)) {
|
||||
throw new AssertionError("Timed out after " + timeout.toMillis() + "ms waiting for " + what);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean reached(String what, Duration timeout, BooleanSupplier condition) {
|
||||
long deadline = System.nanoTime() + timeout.toNanos();
|
||||
while (true) {
|
||||
if (condition.getAsBoolean()) {
|
||||
return true;
|
||||
}
|
||||
if (System.nanoTime() - deadline >= 0L) {
|
||||
return false;
|
||||
}
|
||||
LockSupport.parkNanos(POLL_INTERVAL_NANOS);
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new AssertionError("Interrupted while waiting for " + what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user