Documentation
¶
Overview ¶
Package flowcontrol defines the core plugin interfaces for extending the Flow Control layer.
It establishes the contracts that custom logic, such as queueing disciplines and dispatching policies, must adhere to. By building on these interfaces, the Flow Control layer can be extended and customized without modifying the core controller logic.
The primary contracts are:
- SafeQueue: An interface for concurrent-safe queue implementations.
- FairnessPolicy: The interface for policies that govern the competition between flows.
- OrderingPolicy: The interface for policies that decide the strict sequence of service within a flow.
- IntraFlowDispatchPolicy: (Deprecated) Legacy interface for intra-flow ordering. Replaced by OrderingPolicy.
- ItemComparator: (Deprecated) Legacy interface for exposing ordering logic. Replaced by OrderingPolicy.
These components are linked by QueueCapability, which allows policies to declare their queue requirements (e.g., FIFO or priority-based ordering).
Index ¶
- Variables
- type EvictionFilterPolicy
- type EvictionItem
- type EvictionOrderingPolicy
- type FairnessPolicy
- type FlowControlRequest
- type FlowKey
- type FlowQueueAccessor
- type OrderingPolicy
- type PriorityBandAccessor
- type QueueCapability
- type QueueInspectionMethods
- type QueueItemAccessor
- type QueueItemHandle
- type SaturationDetector
- type UsageLimitPolicy
Constants ¶
This section is empty.
Variables ¶
var ( // ErrIncompatiblePriorityType indicates that a FairnessPolicy attempted to compare items from two different flow // queues whose ItemComparators have different ScoreType values, making a meaningful comparison impossible. ErrIncompatiblePriorityType = errors.New("incompatible priority score type for comparison") )
Functions ¶
This section is empty.
Types ¶
type EvictionFilterPolicy ¶
type EvictionFilterPolicy interface {
plugin.Plugin
// Accept returns true if the request should be tracked in the eviction queue.
Accept(item *EvictionItem) bool
}
EvictionFilterPolicy determines which in-flight requests are eligible for eviction. Only requests for which Accept returns true are added to the eviction queue.
Implementations are singletons registered via plugin.Register and instantiated from config.
type EvictionItem ¶
type EvictionItem struct {
// RequestID is the unique identifier for the request (from x-request-id header).
RequestID string
// Priority is the request's priority level from the InferenceObjective.
Priority int
// DispatchTime is the timestamp when the request was dispatched to the model server.
DispatchTime time.Time
// TargetURL is the base URL of the model server serving this request.
TargetURL string
// Request is the original scheduling request.
Request *scheduling.InferenceRequest
// TargetEndpoint is the metadata of the endpoint serving this request.
TargetEndpoint *datalayer.EndpointMetadata
// EvictCh is closed by the evictor to signal the ext_proc Process() goroutine
// to send an ImmediateResponse and terminate the request.
EvictCh chan struct{}
}
EvictionItem represents an in-flight request that has been dispatched to a model server and may be evicted to free resources.
type EvictionOrderingPolicy ¶
type EvictionOrderingPolicy interface {
plugin.Plugin
// Less returns true if item a should be evicted before item b.
Less(a, b *EvictionItem) bool
}
EvictionOrderingPolicy determines which in-flight request gets evicted first. The eviction queue is a min-heap: the item for which Less returns true is evicted first (popped first).
Implementations are singletons registered via plugin.Register and instantiated from config.
type FairnessPolicy ¶
type FairnessPolicy interface {
plugin.Plugin
// NewState creates the scoped, mutable storage required by this policy for a single Priority Band.
//
// Because the plugin instance itself is shared globally, it cannot hold state like "current round-robin index" or
// "accumulated deficits" inside struct fields. Instead, it creates this state object once per Band.
//
// The Flow Registry manages the lifecycle of this object, storing it on the Priority Band and passing it back to the
// plugin via the PriorityBandAccessor during Pick.
//
// Returns:
// - any: The opaque state object (e.g., &roundRobinCursor{index: 0}).
NewState(ctx context.Context) any
// Pick inspects the active flows in the provided Flow Group (Priority Band) and selects the "winner" for the next
// dispatch attempt.
//
// This is the core logic loop. The implementation should:
// 1. Retrieve its scoped state from band.GetPolicyState().
// 2. Cast the state to its concrete type (e.g., *roundRobinCursor).
// 3. Apply its algorithm to select a FlowQueueAccessor.
// 4. Update the state (e.g., increment the cursor) if necessary.
//
// State may also be updated out-of-band (e.g., from monitoring a metrics server, from integrating with request
// lifecycle hooks, etc.).
//
// Returns:
// - flow: The Flow to service next. Returns nil if no valid candidate is found (e.g., all queues empty).
// - err: Only returned for unrecoverable internal errors. Policies should generally return (nil, nil) if simply
// nothing is eligible.
Pick(ctx context.Context, flowGroup PriorityBandAccessor) (flow FlowQueueAccessor, err error)
}
FairnessPolicy governs the distribution of dispatch opportunities among competing Flows within the same Priority Band.
In simple terms, this policy answers the question: "Which flow gets to dispatch a request next?"
While "Priority" determines strictly which group of flows is serviced first, "Fairness" determines how resources are shared when multiple flows in that same group are fighting for capacity.
Architecture (Flyweight Pattern): Fairness plugins are Singletons. A single instance of a FairnessPolicy handles the logic for potentially many different Priority Bands. To support this, the plugin must be purely functional, separating its Logic (methods) from its State (data).
- Logic: Defined here in the FairnessPolicy interface.
- State: Created via NewState() and stored on the PriorityBandAccessor.
Conformance: Implementations MUST ensure all methods are goroutine-safe.
type FlowControlRequest ¶
type FlowControlRequest interface {
// FlowKey returns the composite key that uniquely identifies the flow instance this request belongs to.
// The `controller.FlowController` uses this key as the primary identifier to look up the correct
// `contracts.ManagedQueue` and configured OrderingPolicy from a `contracts.RegistryShard`.
// The returned key is treated as an immutable value.
FlowKey() FlowKey
// ByteSize returns the request's size in bytes (e.g., prompt size). This is used by the `controller.FlowController`
// for managing byte-based capacity limits and for `contracts.FlowRegistry` statistics.
ByteSize() uint64
// InferenceRequest returns the inference request passed to the scheduling layer.
InferenceRequest() *scheduling.InferenceRequest
// ReceivedTimestamp returns the timestamp when the request was received by the server.
ReceivedTimestamp() time.Time
// InitialEffectiveTTL returns the suggested Time-To-Live for this request.
// This value is treated as a hint; the `controller.FlowController` may override it based on its own configuration or
// policies. A zero value indicates the request has no specific TTL preference, and a system-wide default should be
// applied.
InitialEffectiveTTL() time.Duration
// ID returns an optional, user-facing unique identifier for this specific request. It is intended for logging,
// tracing, and observability. The `controller.FlowController` does not use this ID for dispatching decisions; it uses
// the internal, opaque `QueueItemHandle`.
ID() string
// GetMetadata returns the opaque metadata associated with the request (e.g., header-derived context, subset filters).
// This data is passed transparently to components like contracts.EndpointCandidates to resolve resources (endpoint candidates)
// lazily during the dispatch cycle.
GetMetadata() map[string]any
// InferencePoolName returns the name of the backend pool this request is targeting.
// This is used for observability (metrics labeling) to correlate queue depth with specific backend pools.
InferencePoolName() string
// ModelName returns the name of the base model being requested (e.g., "qwen3-32b").
ModelName() string
// TargetModelName returns the name of the specific adapter or traffic target (e.g., "finance-lora-v1").
TargetModelName() string
}
FlowControlRequest is the contract for an incoming request submitted to the `controller.FlowController`. It represents the "raw" user-provided data and context for a single unit of work.
An object implementing this interface is the primary input to `FlowController.EnqueueAndWait()`. The controller then wraps this object with its own internal structures (which implement `QueueItemAccessor`) to manage the request's lifecycle without modifying the original.
type FlowKey ¶
type FlowKey struct {
// ID is the logical grouping identifier for a flow, such as a tenant ID or a model name. This ID can be shared across
// multiple `FlowKey`s to represent different classes of traffic for the same logical entity. It provides a way to
// group related traffic but does not uniquely identify a manageable flow instance on its own.
ID string
// Priority is the numerical priority level that defines the priority band for this specific flow instance.
//
// A different Priority value for the same ID creates a distinct flow instance. For example, the keys
// `{ID: "TenantA", Priority: 10}` and `{ID: "TenantA", Priority: 100}` are treated as two completely separate,
// concurrently active flows. This allows a single tenant to serve traffic at multiple priority levels simultaneously.
//
// Because the `FlowKey` is immutable, changing the priority of traffic requires using a new `FlowKey`; the old flow
// instance will be automatically garbage collected by the registry when it becomes idle.
Priority int
}
FlowKey is the unique, immutable identifier for a single, independently managed flow instance within the `contracts.FlowRegistry`. It combines a logical grouping `ID` with a specific `Priority` level to form a composite primary key.
The core architectural principle of this model is that each unique, immutable `FlowKey` represents a completely separate stream of traffic with its own queue, lifecycle and statistics.
func (FlowKey) Compare ¶
Compare provides a stable comparison function for two FlowKey instances, suitable for use with sorting algorithms. It returns 1 if the key is less than the other, 0 if they are equal, and -1 if the key is greater than the other. The comparison is performed first by `Priority` (ascending, higher priority first) and then by `ID` (ascending).
type FlowQueueAccessor ¶
type FlowQueueAccessor interface {
QueueInspectionMethods
// OrderingPolicy returns the policy implementation that rules this queue's internal ordering.
// This allows fairness policies (like "global-strict-fairness-policy") to inspect the ordering logic when comparing
// items across queues.
OrderingPolicy() OrderingPolicy
// FlowKey returns the unique, immutable identity of the flow instance this queue belongs to.
// This provides essential context (like the logical grouping ID and Priority) to policies.
FlowKey() FlowKey
}
FlowQueueAccessor represents the runtime state of a single active Flow.
Role in Fairness: To a Fairness Policy, this interface represents a "Candidate": a distinct stream of requests that is competing for dispatch. The policy may inspect the state of this object (e.g., how many requests are waiting, how long they have been waiting, etc.) to decide if it should be the "Winner". Alternatively, it may operate on orthogonal state tracked for each FlowKey.
Conformance: Implementations MUST ensure all methods are goroutine-safe.
type OrderingPolicy ¶
type OrderingPolicy interface {
plugin.Plugin
// Less reports whether item 'a' should be dispatched before item 'b'.
// This makes the policy act as a sort.Interface for the queue.
//
// Invariants:
// - Returning true means 'a' has higher priority than 'b'.
// - If the queue supports CapabilityPriorityConfigurable, this function determines the heap order.
Less(a, b QueueItemAccessor) bool
// RequiredQueueCapabilities returns the set of capabilities that a SafeQueue MUST support to effectively apply this
// policy.
//
// For example:
// - "fcfs-ordering-policy" coupled with CapabilityFIFO is O(1).
// - "edf-ordering-policy" (Earliest Deadline First) REQUIRES CapabilityPriorityConfigurable (Heap) to function
// correctly.
RequiredQueueCapabilities() []QueueCapability
}
OrderingPolicy governs the strict sequence of service within a single Flow.
In simple terms, this policy answers the question: "Which request in this specific queue should be processed next?"
While "Fairness" governs the competition between flows, "Ordering" dictates the internal discipline of a single flow. This allows different flows to have different internal service objectives (e.g., FCFS vs. EDF).
Architecture (Flyweight Pattern): Ordering policies are Singletons. A single instance handles the logic for all queues in a Priority Band. The policy is purely functional.
- Logic: Defined here as a Comparator-centric interface.
- State: Ordering policies are generally stateless, operating on the intrinsic properties of the items.
Conformance: Implementations MUST ensure all methods are goroutine-safe.
type PriorityBandAccessor ¶
type PriorityBandAccessor interface {
// Priority returns the numerical priority level of this group.
Priority() int
// FlowKeys returns the list of identities for every flow currently active within this group.
// The caller can use the ID field from each key to look up a specific queue via the Queue(id) method.
// The order of keys is not guaranteed unless specified by the implementation.
FlowKeys() []FlowKey
// Queue returns the specific Flow (queue) associated with the given logical ID within this group.
// Returns nil if the ID is not found in this group.
Queue(id string) FlowQueueAccessor
// IterateQueues executes the given callback for each active Flow in this group.
// Iteration stops if the callback returns false. The order of iteration is not guaranteed unless specified by
// the implementation.
IterateQueues(callback func(flow FlowQueueAccessor) (keepIterating bool))
// PolicyState retrieves the opaque, mutable state object associated with the active FairnessPolicy for this
// group.
//
// This method allows the Stateless FairnessPolicy plugin (Singleton) to access its Stateful data (Scoped).
// For example, a Round Robin policy uses this to retrieve the cursor indicating which flow was served last
// in this specific group.
//
// Returns:
// - any: The opaque state object. This is guaranteed to be the exact object instance returned by the
// policy's NewState() method during initialization.
PolicyState() any
}
PriorityBandAccessor represents a Priority Band (conceptually, a 'Flow Group')—a collection of flows contending for resources at the same priority level.
In the Endpoint Picker's hierarchy:
- Incoming requests are mapped to a FlowKey.
- All active flows sharing the same FlowKey.Priority are managed within this specific "Flow Group".
- A FairnessPolicy is attached to this group to decide which flow within it gets the next dispatch attempt.
This interface provides the read-only view required by the FairnessPolicy to inspect candidates and the state access required to implement the Flyweight pattern.
Conformance: Implementations MUST ensure all methods are goroutine-safe.
type QueueCapability ¶
type QueueCapability string
QueueCapability defines a functional capability that a SafeQueue implementation can provide. These capabilities allow policies to declare their operational requirements, ensuring that a policy is always paired with a compatible queue.
const ( // CapabilityFIFO indicates that the queue operates in a First-In, First-Out manner. // PeekHead() will return the oldest item (by logical enqueue time). CapabilityFIFO QueueCapability = "FIFO" // CapabilityPriorityConfigurable indicates that the queue's ordering is determined by an ItemComparator. // PeekHead() will return the highest priority item, and PeekTail() will return the lowest priority item according to // this comparator. CapabilityPriorityConfigurable QueueCapability = "PriorityConfigurable" )
type QueueInspectionMethods ¶
type QueueInspectionMethods interface {
// Name returns a string identifier for the concrete queue implementation type (e.g., "ListQueue").
Name() string
// Capabilities returns the set of functional capabilities this queue instance provides.
Capabilities() []QueueCapability
// Len returns the current number of items in the queue.
Len() int
// ByteSize returns the current total byte size of all items in the queue.
ByteSize() uint64
// PeekHead returns the item at the "head" of the queue (the item with the highest priority according to the queue's
// ordering) without removing it.
// Returns nil if the queue is empty.
PeekHead() QueueItemAccessor
// PeekTail returns the item at the "tail" of the queue (the item with the lowest priority according to the queue's
// ordering) without removing it.
// Returns nil if the queue is empty.
PeekTail() QueueItemAccessor
}
QueueInspectionMethods defines SafeQueue's read-only methods.
type QueueItemAccessor ¶
type QueueItemAccessor interface {
// OriginalRequest returns the underlying `FlowControlRequest` that this accessor provides a view of.
// This method serves as an escape hatch, allowing policies or components that are aware of specific
// `FlowControlRequest` implementations to perform type assertions and access richer, application-specific data.
OriginalRequest() FlowControlRequest
// EnqueueTime is the timestamp when the item was logically accepted by the `controller.FlowController` for queuing
// (i.e., when `controller.FlowController.EnqueueAndWait()` was called). It does not reflect the time the request
// landed in a SafeQueue instance.
EnqueueTime() time.Time
// EffectiveTTL is the actual Time-To-Live assigned to this item by the `controller.FlowController`, taking into
// account the request's preference (`FlowControlRequest.InitialEffectiveTTL()`) and any `controller.FlowController`
// or per-flow defaults/policies.
EffectiveTTL() time.Duration
// Handle returns the `QueueItemHandle` associated with this item once it has been successfully added to a
// SafeQueue. It returns nil if the item is not yet in a queue.
Handle() QueueItemHandle
// SetHandle associates a `QueueItemHandle` with this item.
//
// Conformance: This method MUST be called by a SafeQueue implementation within its `Add` method,
// immediately after a new `QueueItemHandle` is created for the item. This ensures that the item always carries a
// valid handle while it is in a queue. This method is not intended for use outside of SafeQueue implementations.
SetHandle(handle QueueItemHandle)
}
QueueItemAccessor provides the internal, enriched, read-only view of a request being managed within the controller.FlowController`'s queues. It is the primary interface through which SafeQueue implementations and policy plugins interact with request data and its associated flow control metadata.
The `controller.FlowController` creates an object that implements this interface by wrapping an incoming `FlowControlRequest`.
type QueueItemHandle ¶
type QueueItemHandle interface {
// Handle returns the underlying, queue-specific raw handle (e.g., `*list.Element`).
// This method is intended for internal use by the SafeQueue implementation that created it.
// Callers outside the queue implementation should treat the returned value as opaque.
Handle() any
// Invalidate marks this handle as no longer valid for future operations.
// This method MUST be called by the SafeQueue implementation itself after the item associated with this handle has
// been removed.
//
// Conformance: Implementations of this method MUST be idempotent.
Invalidate()
// IsInvalidated returns true if this handle has been marked as invalid (e.g., by a call to `Invalidate`).
// A SafeQueue MUST reject any operation that attempts to use an invalidated handle, typically by returning
// ErrInvalidQueueItemHandle.
IsInvalidated() bool
}
QueueItemHandle is an opaque handle to an item that has been successfully added to a SafeQueue. It acts as a key, allowing the `controller.FlowController` to perform targeted operations (like removal) on a specific item without needing to know the queue's internal structure.
A handle is created by and bound to the specific SafeQueue instance that stores the item.
type SaturationDetector ¶
type SaturationDetector interface {
plugin.Plugin
// Saturation returns the aggregate saturation level of the candidate pool.
//
// - A value >= 1.0 indicates that the system is fully saturated. Values strictly > 1.0
// represent the depth of overload, scaling proportionally with the excess load.
// - A value < 1.0 indicates the ratio of used capacity to total available capacity.
//
// The FlowController consumes this signal to make dispatch decisions:
// - If Saturation() >= 1.0: Stop dispatching and apply backpressure (buffer requests).
// - If Saturation() < 1.0: Continue dispatching traffic to the pool.
Saturation(ctx context.Context, endpoints []datalayer.Endpoint) float64
}
SaturationDetector provides real-time load signals.
Plugins implementing this interface provide a continuous saturation gradient [0.0, 1.0+] based on the observed state of the endpoints.
type UsageLimitPolicy ¶
type UsageLimitPolicy interface {
plugin.Plugin
// ComputeLimit calculates usage ceilings for all currently active priority levels based on current
// saturation. The plugin observes the active priority domain (which changes dynamically as workloads
// come and go) and computes relative ceilings from scratch on each call.
//
// Parameters:
// - ctx: Request context for logging, tracing, etc.
// - saturation: Current pool-wide resource saturation as a fraction [0.0, 1.0]
// - priorities: Ordered list of currently active priority levels (highest first)
//
// Returns:
// - ceilings: Computed ceiling for each given priority (n-th ceiling is assigned to the given n-th priority)
// - 0.0 = fully gated (cannot dispatch regardless of current saturation)
// - 1.0 = no gating (can dispatch until fully saturated)
// - Values between 0.0 and 1.0 reserve capacity headroom
//
ComputeLimit(ctx context.Context, saturation float64, priorities []int) (ceilings []float64)
}
UsageLimitPolicy computes the usage limit of a priority band dynamically.
The goal of this policy is to enable adaptive capacity management by gating lower-priority traffic as the pool approaches saturation, reserving headroom for future higher-priority requests.
Saturation represents resource usage as a fraction of total capacity (0.0 = idle, 1.0 = fully saturated) as described in [/pkg/epp/flowcontrol/contracts.SaturationDetector]
Architecture (Stateless Singleton): UsageLimitPolicy plugins are Singletons. A single instance handles limit computation for all priority bands across all shards. The plugin MUST be stateless: it is a pure function that maps the current saturation and active priority domain to a set of ceilings. Any signal conditioning (trend detection, smoothing) belongs in the SaturationDetector layer, not here.
Integration: This policy is called during dispatch decision-making, before a request is allowed to proceed. For each priority band, the returned ceiling is compared against current saturation. If saturation exceeds the ceiling for a given priority, requests at that priority are gated (not dispatched).
Conformance: Implementations MUST ensure all methods are goroutine-safe.