diff --git a/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java b/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java index c445715..d42700a 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java +++ b/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java @@ -17,6 +17,7 @@ import dev.ltms.fleet.peer.PeerHandle; import dev.ltms.fleet.peer.PeerLauncher; import dev.ltms.fleet.peer.PeerUnreachableException; import dev.ltms.fleet.peer.SpawnRequest; +import dev.ltms.fleet.placement.PlacementDecision; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -594,6 +595,23 @@ public abstract class HerdrPeerLauncher implements PeerLauncher { req.sessionName(), spawned.agentSessionId(), spawned.receipt()); } + /** + * {@inheritDoc} + * + *
fleetd #450: re-enters {@link #spawn(SpawnRequest)} with {@code decision}'s profile named + * explicitly. This is the re-entering form the interface javadoc describes for a launcher with + * no placement concept of its own — an instance of this class spawns a single adapter's own + * profile set by explicit name only ({@link #place}/{@link #defaultProfileFor} are unoverridden + * here and just wrap {@link #defaultProfile()}); it does no quarantine/cool-off/maxLoad/model-off + * filtering of its own to re-apply. That filtering lives one layer up, in {@code + * CompositePeerLauncher}, which is the launcher that routes across more than one profile and + * therefore overrides this method with the routing form instead. + */ + @Override + public PeerHandle spawn(SpawnRequest req, PlacementDecision decision) { + return spawn(req.withProfile(decision.profile())); + } + /** The herdr daemon that owns this launcher's pane coordinates. */ public HerdrClient herdr() { return agents.herdr(); diff --git a/fleetd/src/main/java/dev/ltms/fleet/peer/PeerLauncher.java b/fleetd/src/main/java/dev/ltms/fleet/peer/PeerLauncher.java index 7b22325..5bd3f71 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/peer/PeerLauncher.java +++ b/fleetd/src/main/java/dev/ltms/fleet/peer/PeerLauncher.java @@ -253,26 +253,27 @@ public interface PeerLauncher { * matching profile; passing a request that names a different, explicit profile than * the decision it is paired with is a caller bug this method does not attempt to detect. * - *
Default implementation for a launcher with no placement concept of its own: delegates to - * {@link #spawn(SpawnRequest)} with the decision's profile named explicitly — its only spawn - * contract, since there is no separate routing path to honor. This default is correct ONLY for - * a launcher that spawns a single profile of its own (e.g. {@code HerdrPeerLauncher}), where - * the explicit-profile branch it re-enters and the routing branch {@link #place} would have - * used are the same thing. A launcher that routes across more than one profile — the - * way {@code CompositePeerLauncher} routes across every configured adapter — MUST override - * this method instead of inheriting this default. Re-entering {@link - * #spawn(SpawnRequest)} re-applies that single-argument method's explicit-profile checks - * ({@code enforceNotQuarantined}, {@code enforceNotCoolingOff}, {@code enforceMaxLoad}, {@code - * enforceModelEnabled} in {@code CompositePeerLauncher}), which can refuse the very profile - * {@link #place} just chose, if the underlying placement state moved in the window between the - * {@link #place} call and this one — the exact window this method and {@link PlacementDecision} - * exist to close (fleetd #444). + *
No default implementation (fleetd #450): the two correct bodies disagree on purpose, so an
+ * implementer must choose one rather than silently inherit whichever this interface happened to
+ * provide. An implementer with no placement concept of its own — spawns a single profile, e.g.
+ * {@code HerdrPeerLauncher} — should delegate to {@link #spawn(SpawnRequest)} with the decision's
+ * profile named explicitly, since there is no separate routing path to honor there: the
+ * explicit-profile branch it re-enters and the routing branch {@link #place} would have used are
+ * the same thing. A launcher that routes across more than one profile — the way {@code
+ * CompositePeerLauncher} routes across every configured adapter — MUST NOT re-enter {@link
+ * #spawn(SpawnRequest)}. Doing so re-applies that single-argument method's
+ * explicit-profile checks ({@code enforceNotQuarantined}, {@code enforceNotCoolingOff}, {@code
+ * enforceMaxLoad}, {@code enforceModelEnabled} in {@code CompositePeerLauncher}), which can
+ * refuse the very profile {@link #place} just chose, if the underlying placement state moved in
+ * the window between the {@link #place} call and this one — the exact window this method and
+ * {@link PlacementDecision} exist to close (fleetd #444). Before #450 this was a {@code default}
+ * method that only {@code CompositePeerLauncher} overrode; a future placement-doing launcher
+ * could have inherited the re-entering body silently and never known. Making it abstract turns
+ * that silent inheritance into a compile error.
*
* @throws IllegalArgumentException if the decision names an unknown profile
*/
- default PeerHandle spawn(SpawnRequest req, PlacementDecision decision) {
- return spawn(req.withProfile(decision.profile()));
- }
+ PeerHandle spawn(SpawnRequest req, PlacementDecision decision);
/**
* Resolve the effective working directory for a spawn {@code req} without actually spawning.
diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdBackendErrorSinkTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdBackendErrorSinkTest.java
index 432d862..40055a5 100644
--- a/fleetd/src/test/java/dev/ltms/fleet/FleetdBackendErrorSinkTest.java
+++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdBackendErrorSinkTest.java
@@ -20,6 +20,7 @@ import dev.ltms.fleet.peer.PeerLauncher;
import dev.ltms.fleet.peer.SpawnRequest;
import dev.ltms.fleet.placement.BackendOutagePolicy;
import dev.ltms.fleet.placement.BackendQuarantine;
+import dev.ltms.fleet.placement.PlacementDecision;
import dev.ltms.fleet.placement.PlacementPolicies;
import dev.ltms.fleet.session.MemberSession;
import dev.ltms.fleet.session.SessionManager;
@@ -123,6 +124,11 @@ class FleetdBackendErrorSinkTest {
throw new UnsupportedOperationException("not reachable — this test never acquires a session");
}
+ @Override
+ public PeerHandle spawn(SpawnRequest req, PlacementDecision decision) {
+ throw new UnsupportedOperationException("not reachable — this test never acquires a session");
+ }
+
@Override
public Set