From 34c2e4e5604511bafedd5101d8287885e964eaeb Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sun, 6 Sep 2026 13:07:52 -0400 Subject: [PATCH] Reduced admission lock contention - Generation admission uses nonfair metadata lock handoffs - Waiting cutovers retain priority over later generation stages - Concurrent stage tests verify cutover drain and runtime retirement --- .../engine/history/GenerationAdmission.java | 2 +- .../history/GenerationAdmissionTest.java | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/art/arcane/iris/engine/history/GenerationAdmission.java b/core/src/main/java/art/arcane/iris/engine/history/GenerationAdmission.java index 0a3713e46..50420f3dd 100644 --- a/core/src/main/java/art/arcane/iris/engine/history/GenerationAdmission.java +++ b/core/src/main/java/art/arcane/iris/engine/history/GenerationAdmission.java @@ -104,7 +104,7 @@ public final class GenerationAdmission { private State(Path root) { this.root = root; - this.lock = new ReentrantLock(true); + this.lock = new ReentrantLock(); this.changed = lock.newCondition(); } diff --git a/core/src/test/java/art/arcane/iris/engine/history/GenerationAdmissionTest.java b/core/src/test/java/art/arcane/iris/engine/history/GenerationAdmissionTest.java index d01046953..4ce8a7b10 100644 --- a/core/src/test/java/art/arcane/iris/engine/history/GenerationAdmissionTest.java +++ b/core/src/test/java/art/arcane/iris/engine/history/GenerationAdmissionTest.java @@ -5,10 +5,16 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -77,6 +83,37 @@ public class GenerationAdmissionTest { } } + @Test + public void cutoverDrainsContinuouslyArrivingStages() throws Exception { + GenerationAdmission admission = new GenerationAdmission(temporaryFolder.getRoot().toPath()); + GenerationAdmission.RuntimeLease runtime = admission.retainRuntime(); + ExecutorService executor = Executors.newFixedThreadPool(9, Thread.ofPlatform().daemon().factory()); + AtomicBoolean running = new AtomicBoolean(true); + CountDownLatch ready = new CountDownLatch(8); + List> stages = new ArrayList<>(8); + try { + for (int i = 0; i < 8; i++) { + stages.add(executor.submit(() -> enterStagesUntilCutover(admission, running, ready))); + } + assertTrue(ready.await(5, TimeUnit.SECONDS)); + Future cutover = executor.submit(() -> { + try (GenerationAdmission.CutoverLease ignored = admission.beginCutover()) { + running.set(false); + } + }); + cutover.get(5, TimeUnit.SECONDS); + for (Future stage : stages) { + stage.get(5, TimeUnit.SECONDS); + } + } finally { + running.set(false); + executor.shutdownNow(); + assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + runtime.close(); + } + assertThrows(IllegalStateException.class, admission::enterStage); + } + @Test public void aStageMayCompleteOnAThreadOtherThanItsCaller() throws Exception { GenerationAdmission admission = new GenerationAdmission(temporaryFolder.getRoot().toPath()); @@ -197,6 +234,18 @@ public class GenerationAdmissionTest { } } + private static void enterStagesUntilCutover(GenerationAdmission admission, AtomicBoolean running, CountDownLatch ready) { + boolean started = false; + while (running.get() && !Thread.currentThread().isInterrupted()) { + try (GenerationAdmission.StageLease ignored = admission.enterStage()) { + if (!started) { + started = true; + ready.countDown(); + } + } + } + } + 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) {