From 37b4031ca4e202d036116afa5379e6d8b6ec7383 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Thu, 13 Aug 2026 21:12:32 +0200 Subject: [PATCH] CB-553: enforce maxLoad on explicit-profile spawns (no cap bypass) --- .../bridged/worker/CompositePeerLauncher.java | 43 ++++++++++++++++- .../worker/CompositePeerLauncherTest.java | 48 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/bridged/src/main/java/dev/ltms/bridged/worker/CompositePeerLauncher.java b/bridged/src/main/java/dev/ltms/bridged/worker/CompositePeerLauncher.java index 938eb08..ed468fa 100644 --- a/bridged/src/main/java/dev/ltms/bridged/worker/CompositePeerLauncher.java +++ b/bridged/src/main/java/dev/ltms/bridged/worker/CompositePeerLauncher.java @@ -9,6 +9,7 @@ import dev.ltms.bridged.peer.PeerUnreachableException; import dev.ltms.bridged.peer.SpawnRequest; import dev.ltms.bridged.placement.PlacementCandidate; import dev.ltms.bridged.placement.PlacementContext; +import dev.ltms.bridged.placement.PlacementException; import dev.ltms.bridged.placement.PlacementPolicies; import dev.ltms.bridged.placement.PlacementPolicy; import org.slf4j.Logger; @@ -139,8 +140,12 @@ public final class CompositePeerLauncher implements PeerLauncher { public PeerHandle spawn(SpawnRequest req) { String requestedProfile = req.profileName(); if (requestedProfile != null && !requestedProfile.isBlank()) { - // An explicit profile bypasses the policy entirely. + // An explicit profile bypasses the placement policy, but not the capacity cap: maxLoad + // is documented as an unconditional limit on this profile (BridgedConfig.Worker), and + // the charter makes explicit-profile spawns the normal path — so skipping the check + // here would leave the cap dead config in real operation. HerdrPeerLauncher d = route(requestedProfile); + enforceMaxLoad(requestedProfile); PeerHandle handle = d.spawn(req); spawnedBy.put(handle.id(), d); return handle; @@ -190,6 +195,42 @@ public final class CompositePeerLauncher implements PeerLauncher { + " candidate(s): " + String.join(", ", unreachable)); } + /** + * Refuse an explicit-profile spawn when the profile is at its {@code maxLoad} cap. + * + *

maxLoad is a documented, unconditional capacity limit (see {@code BridgedConfig.Worker#maxLoad}), + * and the charter makes explicit-profile spawns the normal path — so enforcing it only in placement + * ({@link dev.ltms.bridged.placement.PlacementPolicyUtil}) would leave the cap dead config on every + * call that names a profile. Same rule as placement: {@code live >= cap} is at capacity. + * + *

Deliberately no fallback to another profile: the caller named {@code profile} for a cost/model + * reason, and silently re-routing a paid-tier (subscription) request elsewhere is worse than + * refusing it. A caller that wants placement should omit the profile and let the policy pick. + * + *

Known TOCTOU limitation — documented, not fixed. {@link #liveCount} is read outside any lock and + * {@code SessionManager} registers a session only after {@code launcher.spawn} returns, so two + * genuinely concurrent spawns can both pass this check. The race already exists on the placement + * path. Closing it needs slot reservation in the registry; serializing spawn here would block on + * the readiness gate and is a far worse trade. + * + * @param profile the profile the caller explicitly named + * @throws PlacementException when the profile is at capacity + */ + private void enforceMaxLoad(String profile) { + // Absent config, or a config whose maxLoad normalized to null (non-positive ⇒ unlimited at + // load), means no cap — never cap what wasn't configured. + BridgedConfig.Worker cfg = profileConfigs.get(profile); + Integer cap = (cfg == null) ? null : cfg.maxLoad(); + if (cap == null) { + return; + } + int live = liveCount.apply(profile); + if (live >= cap) { + throw new PlacementException("worker profile '" + profile + "' is at maxLoad: " + live + + " live >= " + cap + " cap; refusing spawn — no fallback to another profile"); + } + } + /** Build the candidate list from the configured profiles, in definition order. */ private List candidates() { List out = new ArrayList<>(); diff --git a/bridged/src/test/java/dev/ltms/bridged/worker/CompositePeerLauncherTest.java b/bridged/src/test/java/dev/ltms/bridged/worker/CompositePeerLauncherTest.java index 518e789..0f74094 100644 --- a/bridged/src/test/java/dev/ltms/bridged/worker/CompositePeerLauncherTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/worker/CompositePeerLauncherTest.java @@ -378,6 +378,54 @@ class CompositePeerLauncherTest { assertEquals(1, adapter.spawnCount("b")); } + @Test + void explicitSpawnAtMaxLoadThrowsPlacementExceptionNamingProfileLiveAndCap() { + FakeHerdr herdr = new FakeHerdr(); + Map profiles = ordered( + "a", stubWorker("a", 1.0f, 2), + "b", stubWorker("b")); + StubLauncher adapter = new StubLauncher("claude", herdr, profiles, "a", Set.of()); + CompositePeerLauncher composite = new CompositePeerLauncher( + List.of(adapter), "a", profiles, PlacementPolicies.fixed(), name -> "a".equals(name) ? 2 : 0); + + PlacementException e = assertThrows(PlacementException.class, + () -> composite.spawn(new SpawnRequest("a", null, null))); + assertTrue(e.getMessage().contains("'a'"), "message names the profile: " + e.getMessage()); + assertTrue(e.getMessage().contains("2 live"), "message names the live count: " + e.getMessage()); + assertTrue(e.getMessage().contains("2 cap"), "message names the cap: " + e.getMessage()); + assertEquals(0, adapter.spawnCount("a"), "at cap, the spawn is refused before any delegation"); + } + + @Test + void explicitSpawnUnderMaxLoadStillSucceeds() { + FakeHerdr herdr = new FakeHerdr(); + Map profiles = ordered( + "a", stubWorker("a", 1.0f, 2), + "b", stubWorker("b")); + StubLauncher adapter = new StubLauncher("claude", herdr, profiles, "a", Set.of()); + CompositePeerLauncher composite = new CompositePeerLauncher( + List.of(adapter), "a", profiles, PlacementPolicies.fixed(), name -> "a".equals(name) ? 1 : 0); + + PeerHandle h = composite.spawn(new SpawnRequest("a", null, null)); + assertEquals("a", h.profile(), "a profile under its cap accepts an explicit spawn"); + assertEquals(1, adapter.spawnCount("a"), "the under-cap spawn is delegated"); + } + + @Test + void explicitSpawnWithNullMaxLoadIsNeverCapped() { + FakeHerdr herdr = new FakeHerdr(); + Map profiles = ordered( + "a", stubWorker("a", 1.0f, null), + "b", stubWorker("b")); + StubLauncher adapter = new StubLauncher("claude", herdr, profiles, "a", Set.of()); + // A deliberately absurd live count: an unset maxLoad means unlimited, so it must never refuse. + CompositePeerLauncher composite = new CompositePeerLauncher( + List.of(adapter), "a", profiles, PlacementPolicies.fixed(), name -> 1000); + + PeerHandle h = composite.spawn(new SpawnRequest("a", null, null)); + assertEquals("a", h.profile(), "a profile with no maxLoad is never capped, however many live workers"); + } + @Test void emptyCandidateSetThrowsClearException() { FakeHerdr herdr = new FakeHerdr(); -- 2.52.0