Reduced generation routing contention

- Ready runtime leases avoid repeated metadata lock acquisition
- Concurrent routing retains shutdown and history ownership checks
This commit is contained in:
Brian Neumann-Fopiano
2026-09-06 12:09:49 -04:00
2 changed files with 52 additions and 1 deletions
@@ -51,7 +51,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
this.runtimeFactory = Objects.requireNonNull(runtimeFactory, "activation runtime factory");
this.bindings = new LinkedHashMap<>();
this.retiringBindings = new LinkedHashMap<>();
this.stateLock = new ReentrantLock(true);
this.stateLock = new ReentrantLock();
this.inactive = stateLock.newCondition();
this.operationDepth = ThreadLocal.withInitial(() -> 0);
this.scopedRoute = new ThreadLocal<>();
@@ -449,6 +449,9 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
loader = true;
}
entry.leases++;
if (!entry.loading && entry.loadFailure == null && entry.binding != null) {
return new RuntimeLease(this, entry, entry.binding);
}
} finally {
stateLock.unlock();
}
@@ -584,6 +587,9 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
}
private List<RuntimeRetirement> collectEvictionsLocked() {
if (bindings.size() <= 1) {
return List.of();
}
ArrayList<RuntimeRetirement> retired = new ArrayList<>();
while (bindings.size() > 1) {
RuntimeCacheEntry candidate = null;
@@ -389,6 +389,33 @@ public final class GenerationHistoryRuntimeRouterTest {
verify(engine, never()).closeDetachedGenerationRuntime(active);
}
@Test
public void closeCompletesWhileWorkersContinuouslyAcquireReadyRoutes() throws Exception {
Path world = temporaryFolder.newFolder("router-contended-close-world").toPath();
GenerationHistory history = createHistory(world, createPack("router-contended-close-pack", "alpha"));
IrisEngine engine = mock(IrisEngine.class);
FakeRuntimeFactory runtimes = new FakeRuntimeFactory();
IrisEngine.GenerationRuntimeBinding active = runtimes.binding(history, history.activeActivation());
when(engine.getActiveGenerationRuntimeBinding()).thenReturn(active);
ExecutorService executor = Executors.newFixedThreadPool(9);
CountDownLatch routing = new CountDownLatch(8);
CompletableFuture<?>[] workers = new CompletableFuture<?>[8];
try (GenerationHistoryRuntimeRouter router = GenerationHistoryRuntimeRouter.attach(
engine, history, (ignored, x, z) -> signature(x, z), runtimes)) {
for (int i = 0; i < workers.length; i++) {
workers[i] = CompletableFuture.runAsync(() -> routeUntilClosed(router, routing), executor);
}
assertTrue(routing.await(5, TimeUnit.SECONDS));
CompletableFuture.runAsync(router::close, executor).get(5, TimeUnit.SECONDS);
CompletableFuture.allOf(workers).get(5, TimeUnit.SECONDS);
verify(engine).detachGenerationHistoryRuntimeRouter(router);
verify(engine, never()).closeDetachedGenerationRuntime(active);
} finally {
executor.shutdownNow();
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
}
}
@Test
public void closeLeavesTheSingleDefaultRuntimeOwnedByTheEngine() throws Exception {
Path world = temporaryFolder.newFolder("router-close-current-world").toPath();
@@ -894,6 +921,24 @@ public final class GenerationHistoryRuntimeRouterTest {
router.close();
}
private static void routeUntilClosed(GenerationHistoryRuntimeRouter router, CountDownLatch routing) {
boolean started = false;
while (!Thread.currentThread().isInterrupted()) {
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = router.openRoute(0, 0)) {
assertEquals(1L, route.activation().activationId());
if (!started) {
started = true;
routing.countDown();
}
} catch (IOException failure) {
throw new AssertionError(failure);
} catch (IllegalStateException closed) {
assertEquals("Generation-history runtime router is closed.", closed.getMessage());
return;
}
}
}
private AtomicReference<IrisEngine.GenerationRuntimeBinding> installScopeTracking(IrisEngine engine) {
AtomicReference<IrisEngine.GenerationRuntimeBinding> scoped = new AtomicReference<>();
when(engine.openGenerationRuntimeScope(any())).thenAnswer(invocation -> {