Matching and selection
Worker choice has two different stages. Eligibility removes clients and contexts that cannot run the task. Selection chooses one candidate from what remains. Keep hard requirements in the first stage; a preference or scoring strategy is not a substitute for a correctness boundary.
Eligibility begins with live presence
Every acquisition reads IConnectionRegistry.get_presence(user_id). A context is selectable only when its client appears in that heartbeat-backed presence view. An old state report by itself is not enough.
The manager then applies these filters:
requirementscompares exact top-level key/value pairs with each connection'sclient_metadata;client_typelimits contexts tobrowser,console,mobile, orweb;- the context must be worker-owned, unassigned, and
activeoridle; - browser placement can add window and capacity conditions.
Requirement matching is deliberately small. It is exact equality, not a nested query language, range expression, or capability implication system. A request such as {"region": "eu", "account": "billing"} requires both fields to be present with those exact values.
Connection metadata is useful only to the degree that the product controls its source. Do not put a security decision in self-asserted client metadata unless authentication or admission policy already established that the client may make that assertion.
Affinity is a preference
affinity_client_id tries one connected eligible client first. If it has no assignable worker, the manager can choose another eligible client. Use affinity for cache locality, continuity, or performance where fallback is acceptable.
Use a hard metadata requirement or a product-owned selection boundary when fallback would be incorrect. In particular, affinity does not turn a client ID into an authorization rule.
Choose a strategy for a measurable objective
The optional dispatch strategy sees only the idle candidates that passed eligibility.
| Strategy | Selection rule | Important limit |
|---|---|---|
| no explicit strategy | asks eligible clients for an assignable worker | no fairness or stable global ordering promise |
RoundRobinStrategy | cycles through candidates sorted by client and context ID | counter is in one manager process and per user |
LeastBusyStrategy | prefers the client with the fewest currently assigned workers | measures registry assignments, not CPU, page cost, or a durable queue |
ScoringStrategy | chooses the highest application-supplied numeric score | scorer owns meaning, tie behavior, and exceptions |
CompositeStrategy | uses the first strategy in the chain that returns a candidate | combines fallbacks, not scores; all None means no existing worker was selected |
For example, a single-process application can request round-robin selection without changing the pool contract:
from servercheetah.services import RoundRobinStrategy, WorkerContextManager
wcm = WorkerContextManager(
context_registry=context_registry,
connection_registry=connection_registry,
app_node=app_node,
dispatch_strategy=RoundRobinStrategy(),
)
Do not infer distributed fairness from these strategies. Several manager processes maintain separate counters and can observe different snapshots. When scheduling order, quotas, or priority are product invariants, keep them in an application-owned coordinator and use WCM for the final live-context assignment.
Atomic assignment settles races
Querying and strategy selection do not reserve a worker. The manager calls assign_task(user_id, client_id, context_id, task_id) after selection. If another caller won the candidate, the registry returns False; the manager removes that context and asks the strategy to choose from the remaining candidates.
This retry protects against ordinary selection races. The concrete registry controls the scope of atomicity: in-memory registries coordinate one process, while Redis registries use shared mutation primitives for several processes. Neither turns the product task into a distributed transaction.
Auto-provision only after selection fails
When no existing candidate is assigned and auto_provision=True, the manager may create a context on an eligible connected client. Current generic provisioning supports browser and console clients. Mobile and web contexts can participate when already reported as workers, but the manager does not know how to create them.
Browser creation has additional placement and capacity rules documented in Browser placement and protection. Console creation sends create_flow(worker=True), waits for the flow to appear in the context registry, and then assigns it. The negotiated max_flows value is not currently enforced by WCM, so the product must bound console auto-provisioning separately.
Selection failure returns None; it is not an instruction to retry immediately. The application should distinguish temporary absence, hard requirements, capacity, and uncertain prior work in its own task policy.