@@ -56,8 +56,10 @@ import (
5656 "github.com/llm-d/llm-d-router/pkg/epp/flowcontrol"
5757 "github.com/llm-d/llm-d-router/pkg/epp/flowcontrol/contracts"
5858 fccontroller "github.com/llm-d/llm-d-router/pkg/epp/flowcontrol/controller"
59+ fceviction "github.com/llm-d/llm-d-router/pkg/epp/flowcontrol/eviction"
5960 fcregistry "github.com/llm-d/llm-d-router/pkg/epp/flowcontrol/registry"
6061 fwkdl "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/datalayer"
62+ fwkfc "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/flowcontrol"
6163 fwkplugin "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/plugin"
6264 attrconcurrency "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/concurrency"
6365 attrlatency "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/latency"
@@ -70,6 +72,8 @@ import (
7072 sourcemetrics "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/metrics"
7173 srcmodels "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/models"
7274 sourcenotifications "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/notifications"
75+ evictfiltering "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/eviction/filtering"
76+ evictordering "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/eviction/ordering"
7377 "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/fairness/globalstrict"
7478 programaware "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/fairness/program-aware"
7579 "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/fairness/roundrobin"
@@ -448,9 +452,12 @@ func (r *Runner) setup(ctx context.Context, cfg *rest.Config, opts *runserver.Op
448452
449453 endpointCandidates := contracts .EndpointCandidates (requestcontrol .NewDatastoreEndpointCandidates (ds ,
450454 requestcontrol .WithDisableEndpointSubsetFilter (opts .DisableEndpointSubsetFilter )))
451- endpointCandidates , admissionController , priorityBandControlPlane := r .initAdmissionControl (ctx , opts , eppConfig , endpointCandidates )
455+ endpointCandidates , admissionController , priorityBandControlPlane , requestEvictor := r .initAdmissionControl (ctx , opts , eppConfig , endpointCandidates )
452456
453457 director := requestcontrol .NewDirectorWithConfig (ds , scheduler , admissionController , endpointCandidates , r .requestControlConfig )
458+ if requestEvictor != nil {
459+ director .SetRequestEvictor (requestEvictor )
460+ }
454461
455462 serverRunner := & runserver.ExtProcServerRunner {
456463 GrpcPort : opts .GRPCPort ,
@@ -471,6 +478,9 @@ func (r *Runner) setup(ctx context.Context, cfg *rest.Config, opts *runserver.Op
471478 GRPCMaxSendMsgSize : opts .GRPCMaxSendMsgSize ,
472479 EnableGRPCStreamMetrics : opts .EnableGRPCStreamMetrics ,
473480 }
481+ if requestEvictor != nil {
482+ serverRunner .EvictChannelLookup = requestEvictor .EvictionRegistry ()
483+ }
474484
475485 if err := serverRunner .SetupWithManager (mgr ); err != nil {
476486 setupLog .Error (err , "Failed to setup EPP controllers" )
@@ -876,28 +886,87 @@ func (r *Runner) initAdmissionControl(
876886 opts * runserver.Options ,
877887 eppConfig * config.Config ,
878888 endpointCandidates contracts.EndpointCandidates ,
879- ) (contracts.EndpointCandidates , requestcontrol.AdmissionController , contracts.PriorityBandControlPlane ) {
889+ ) (contracts.EndpointCandidates , requestcontrol.AdmissionController , contracts.PriorityBandControlPlane , * fceviction. RequestEvictor ) {
880890 if ! r .featureGates [flowcontrol .FeatureGate ] {
881891 setupLog .Info ("Experimental Flow Control layer is disabled, using legacy admission control" )
882892 return endpointCandidates ,
883893 requestcontrol .NewLegacyAdmissionController (eppConfig .SaturationDetector , endpointCandidates ),
894+ nil ,
884895 nil
885896 }
886897 endpointCandidates = requestcontrol .NewCachedEndpointCandidates (ctx , endpointCandidates , 50 * time .Millisecond )
887898 setupLog .Info ("Initializing experimental Flow Control layer" )
888899 registry := fcregistry .NewFlowRegistry (eppConfig .FlowControlConfig .Registry , setupLog )
889- fc := fccontroller .NewFlowController (
890- ctx ,
891- opts .PoolName ,
892- eppConfig .FlowControlConfig .Controller ,
893- fccontroller.Deps {
894- Registry : registry ,
895- SaturationDetector : eppConfig .SaturationDetector ,
896- EndpointCandidates : endpointCandidates ,
897- UsageLimitPolicy : eppConfig .FlowControlConfig .UsageLimitPolicy ,
898- },
899- )
900- return endpointCandidates , requestcontrol .NewFlowControlAdmissionController (fc , opts .PoolName ), registry
900+
901+ deps := fccontroller.Deps {
902+ Registry : registry ,
903+ SaturationDetector : eppConfig .SaturationDetector ,
904+ EndpointCandidates : endpointCandidates ,
905+ UsageLimitPolicy : eppConfig .FlowControlConfig .UsageLimitPolicy ,
906+ }
907+
908+ var requestEvictor * fceviction.RequestEvictor
909+ if eppConfig .FlowControlConfig .Controller .EnableEviction {
910+ var err error
911+ requestEvictor , err = buildRequestEvictor ()
912+ if err != nil {
913+ setupLog .Error (err , "Failed to build eviction plumbing; in-flight eviction disabled" )
914+ } else {
915+ deps .InFlightEvictor = requestEvictor
916+ grace := deriveEvictionConfirmationGrace (eppConfig .SaturationDetector , opts )
917+ eppConfig .FlowControlConfig .Controller .EvictionConfirmationGrace = grace
918+ setupLog .Info ("In-flight eviction plumbing initialized" ,
919+ "filter" , evictfiltering .SheddableFilterType ,
920+ "ordering" , evictordering .PriorityThenTimeOrderingType ,
921+ "confirmationGrace" , grace )
922+ }
923+ }
924+
925+ fc := fccontroller .NewFlowController (ctx , opts .PoolName , eppConfig .FlowControlConfig .Controller , deps )
926+ return endpointCandidates , requestcontrol .NewFlowControlAdmissionController (fc , opts .PoolName ), registry , requestEvictor
927+ }
928+
929+ const (
930+ // evictionEngineReclaimBudget covers the engine-side abort and KV garbage-collection time
931+ // between a revoked request's stream termination and the freed capacity appearing in scraped
932+ // metrics.
933+ evictionEngineReclaimBudget = 250 * time .Millisecond
934+ // evictionLeadingSensorGrace covers scheduling jitter for sensors whose gauge updates in the
935+ // same event chain as the confirmation (a few dispatch ticks).
936+ evictionLeadingSensorGrace = 10 * time .Millisecond
937+ )
938+
939+ // deriveEvictionConfirmationGrace computes the reclamation pacing grace from the paired saturation
940+ // detector's confirmation-to-visibility lag. The grace is a property of the sensor, not a
941+ // preference, so it is derived rather than configured (see docs/flow-control-eviction.md).
942+ func deriveEvictionConfirmationGrace (detector fwkfc.SaturationDetector , opts * runserver.Options ) time.Duration {
943+ if detector != nil && detector .TypedName ().Type == concurrency .ConcurrencyDetectorType {
944+ // The concurrency detector's counters decrement when the stream terminates: the gauge leads
945+ // physical reclamation, and only dispatch-loop jitter separates confirmation from visibility.
946+ return evictionLeadingSensorGrace
947+ }
948+ // Scraped sensors (utilization detector, or any custom detector, conservatively): the freed
949+ // capacity is visible only after the engine reclaims it and the next non-stale scrape lands.
950+ return opts .RefreshMetricsInterval + opts .MetricsStalenessThreshold + evictionEngineReclaimBudget
951+ }
952+
953+ // buildRequestEvictor assembles the in-flight eviction plumbing: the victim filter and ordering
954+ // policies, the ImmediateResponse eviction mechanism, and the tracking queue that ties them
955+ // together. See docs/flow-control-eviction.md.
956+ func buildRequestEvictor () (* fceviction.RequestEvictor , error ) {
957+ orderingPlugin , err := evictordering .PriorityThenTimeOrderingFactory (evictordering .PriorityThenTimeOrderingType , nil , nil )
958+ if err != nil {
959+ return nil , fmt .Errorf ("failed to create eviction ordering policy: %w" , err )
960+ }
961+ filterPlugin , err := evictfiltering .SheddableFilterFactory (evictfiltering .SheddableFilterType , nil , nil )
962+ if err != nil {
963+ return nil , fmt .Errorf ("failed to create eviction filter policy: %w" , err )
964+ }
965+ return fceviction .NewRequestEvictor (
966+ orderingPlugin .(fwkfc.EvictionOrderingPolicy ),
967+ filterPlugin .(fwkfc.EvictionFilterPolicy ),
968+ fceviction .NewImmediateResponseEvictor (),
969+ ), nil
901970}
902971
903972// runWithFileDiscovery handles the execution path when a discovery plugin is configured.
@@ -974,8 +1043,11 @@ func (r *Runner) runWithFileDiscovery(ctx context.Context, opts *runserver.Optio
9741043 requestcontrol .WithDisableEndpointSubsetFilter (opts .DisableEndpointSubsetFilter )))
9751044 // File-discovery mode has no InferenceObjective reconciler to drive the
9761045 // control plane; static bands from config apply at registry construction.
977- endpointCandidates , admissionController , _ := r .initAdmissionControl (ctx , opts , eppConfig , endpointCandidates )
1046+ endpointCandidates , admissionController , _ , requestEvictor := r .initAdmissionControl (ctx , opts , eppConfig , endpointCandidates )
9781047 director := requestcontrol .NewDirectorWithConfig (ds , scheduler , admissionController , endpointCandidates , r .requestControlConfig )
1048+ if requestEvictor != nil {
1049+ director .SetRequestEvictor (requestEvictor )
1050+ }
9791051
9801052 gknn := common.GKNN {
9811053 NamespacedName : types.NamespacedName {Name : poolName , Namespace : namespace },
@@ -998,6 +1070,9 @@ func (r *Runner) runWithFileDiscovery(ctx context.Context, opts *runserver.Optio
9981070 GRPCMaxSendMsgSize : opts .GRPCMaxSendMsgSize ,
9991071 EnableGRPCStreamMetrics : opts .EnableGRPCStreamMetrics ,
10001072 }
1073+ if requestEvictor != nil {
1074+ serverRunner .EvictChannelLookup = requestEvictor .EvictionRegistry ()
1075+ }
10011076
10021077 r .customCollectors = append (r .customCollectors , collectors .NewInferencePoolMetricsCollector (ds ))
10031078 metrics .Register (r .customCollectors ... )
0 commit comments