6.3.8 Scheduling Actions on Remote Executors

An action can be ready for remote execution and still wait forever. Its inputs, tools, and execution properties may describe a compatible worker correctly, yet the compatible pool can be busy, a queue can favor other work indefinitely, or the service can keep accepting work long after it can give a useful response. That is a scheduling problem, not proof that the action was mismatched.

6.3.2 Remote Executor Matching established which destinations can fulfill an action's request. Start this investigation with that already-valid match set. The scheduler's job is to decide whether to admit the action, where to hold it while it waits, and when to give one compatible executor permission to begin.

The portable state is not the scheduler policy

REAPI gives the client an execution request and a long-running Operation to observe. It deliberately does not prescribe the server's queues, admission rules, priority-policy mapping, leases, worker protocol, or fairness rule.1 QUEUED, EXECUTING, and COMPLETED can therefore describe useful client observations without answering the operational questions below. In particular, EXECUTING is portable evidence that the remote execution system currently reports worker execution. Backend evidence is still needed to identify and reconcile the specific worker, current dispatch, assignment or lease, queue, and policy path.

QuestionEvidence that can answer itDo not infer it from
Was this work admitted?A backend admission decision, reason, and cohort or request correlationA non-terminal Operation alone
Where can it wait?The implementation's compatible queue or pool identityThe action's platform properties alone
Which worker or claim is current?A backend dispatch, assignment, or lease joined to an executorEXECUTING alone
Is it receiving fair service?Per-cohort wait and dispatch evidence over timeA fleet-wide average or queue depth
Is the service overloaded safely?A stated defer, reject, or backpressure outcome and its reasonThe fact that requests still receive Operations

That separation prevents a common bad diagnosis: treating a stale assignment as the current worker or claim for an action that reports EXECUTING, or calling a large pool "available" when none of its workers can run the queued class. Keep the portable Operation name and stage and the backend queue, dispatch, lease, worker, and policy-revision identifiers as different fields in the same trace. The full portable-to-backend trace is developed in 6.3.4 Remote Action Tracing.

Schedule the compatible set, not the whole fleet

For every accepted action, make the control path observable:

  1. Admission decides whether this request may consume a bounded service population now. Record the cohort, limits consulted, and an explicit accepted, deferred, or rejected result.
  2. Queue selection places accepted work only where its already-established compatible executors can obtain it. A global queue can still work, but the scheduler must expose how it preserves compatibility.
  3. Dispatch or lease gives one executor the current right to work. That right needs an implementation-specific expiry, loss, and reconciliation contract; it is not a REAPI guarantee.
  4. Completion accounting releases the relevant limit only when the backend's chosen witness says the attempt is no longer consuming it. Do not silently count an expired lease as healthy running work.

This is why a generic request load balancer is often a poor substitute for a scheduler. Build actions have highly uneven duration and resource demands, so queue length is not a reliable prediction of wait time. One implementation uses late binding: it offers work to several executor-local queues and lets the first eligible executor obtain an exclusive claim.2 That is useful implementation evidence, not a portable algorithm to copy.

Priority needs a limit and fairness needs a witness

REAPI can carry an optional priority hint, but the server decides the supported range and how that hint maps to scheduling policy. Priority in that policy says which eligible work should go first during contention. It does not decide how much work a cohort may inject, how long lower-priority work may wait, or when the service must stop accepting work. Without those other controls, a continuous stream of urgent requests can turn a lower-priority queue into invisible starvation.

Choose backend policy per named workload cohort—for example, interactive, presubmit, release, or background work—and make three promises testable:

  • Quota or concurrency limit: the largest named share a cohort may occupy. It protects other work from one runaway build without claiming that the queue itself supplies tenant isolation.
  • Fairness rule: the service it promises among eligible cohorts, such as a bounded wait target or a documented weighted/round-robin rule. Measure the result per compatible pool and cohort, not only across all workers.
  • Backpressure rule: what happens before the service becomes an unbounded waiting room. It may defer, reject, or ask an upstream submitter to slow down; record the decision and reason so callers can distinguish overload from a failed action.

These mechanisms are backend choices. For example, one BuildBuddy deployment uses tenant queues with round-robin service and priority queues, while its executors obtain an exclusive lease before running a dequeued action.3 That demonstrates a possible combination of fairness, priority, and leases; it does not make its queue structure or flags part of Bazel or REAPI.

Do not turn this into an organization-wide ranking debate. This article needs the scheduler contract that enforces a chosen policy. Deciding who may declare a cohort urgent and governing that authority require an organization's policy process. Likewise, one queue or quota is not an end-to-end security boundary; 6.3.14 Multi-Tenant Remote Execution connects identity, storage, executor, and result controls into that larger guarantee.

Diagnose an overload with a joined trace

Suppose interactive actions in a Linux pool are slow while the dashboard shows idle macOS workers and a modest global queue. Neither observation disproves overload. The macOS workers are not matchable capacity for these actions, and the global depth hides which Linux cohort is waiting.

Start with a small cohort and time window. For representative actions, join the action and Operation identifiers to the admission decision, compatible pool, queue-entered time, every dispatch or lease transition, executor identity, and terminal attempt outcome. Then compare the following facts in one clock domain:

SignalIt helps distinguishIt cannot establish alone
Queue age by compatible pool and cohortA protected cohort waiting behind usable demandCommand runtime or total fleet health
Admission deferrals or rejections by reasonIntentional bounded overload from an unexplained disappearanceWhether a rejected action was ever executed
Dispatch/lease age and expiry or loss countStalled or stale ownership from fresh executionThat the command completed or outputs published
Matchable idle workers and resource stateA scheduling/policy issue from merely nominal capacityWhether the pool is large enough for the workload
Per-cohort wait percentiles and starvation countWhether the stated fairness promise is being metWhether all tenants are isolated end to end

Queue age is especially useful when paired with the service's limit. An incident with a long horizontal queue-age line turned out to be an internal one-hour reject limit after a single build submitted a vast number of actions; the useful follow-up was a shorter timeout, build cancellation, and per-build guardrails, not a claim that the scheduler's queue was broken.4 The same source notes that careless proxy backpressure can keep a finished worker from appearing available, so "busy" must be attributed to the actual backend state rather than guessed from a client stream.4

If the trace shows enough eligible work and a sustained lack of matchable capacity, take the measured arrival, duration, resource, and scale-lag evidence to 6.3.12 Remote Execution Capacity. Do not enlarge a pool merely because a global queue was nonempty. If it shows a bad mapping or no compatible executor, return to 6.3.2 Remote Executor Matching instead.

think

Decide: A low-priority Linux test has waited for 25 minutes. The global queue is short, and the dashboard reports many idle workers. What is the first evidence request that separates starvation from a compatibility or capacity problem?

Reveal

Request the test's compatible pool and cohort, its queue-entered time, the policy and quota decisions, and the state of matchable workers in that pool. If the pool has eligible capacity but the cohort receives no dispatches, inspect the fairness and priority policy. If it has no eligible capacity, the next question is matching or capacity—not the fleet-wide idle count.

key takeaway

Scheduling begins after compatibility is known. Treat admission, queues, dispatch or leases, priority, quotas, fairness, and backpressure as named backend contracts, and keep them separate from the portable REAPI Operation a client observes. A scheduling trace must show how a matchable action moved—or did not move—through those contracts.

Under overload, protect named cohorts with explicit limits and a visible defer/reject outcome. Diagnose per compatible pool and cohort using queue age, dispatch/lease state, and matchable capacity; a global queue, fleet average, or nominal idle worker count cannot prove fairness or explain latency.

Check your understanding · 3 questions

1.A client receives a non-terminal REAPI Operation for an action, and its metadata later reports EXECUTING. What still requires backend-specific evidence?

Select one answer

2.Which observations help distinguish a fair-scheduling failure from a capacity or compatibility problem for one delayed cohort?

Select all that apply

3.True or false: separate portable execution observations from backend scheduling policy.

Choose True or False for each sentence

REAPI standardizes a universal queue topology and lease-expiry rule for remote executors.
Priority alone prevents a continuous urgent workload from starving lower-priority work.
A visible defer or reject outcome can make overload bounded and diagnosable.
An expired lease should be reconciled through the backend contract instead of counted automatically as healthy execution.
0 of 3 answered

Footnotes

  1. Remote APIs — protocol contracts for caching and remote execution — REAPI defines client-facing wire contracts, while scheduler topology, queues, and worker protocols are implementation-specific.

  2. Distributed Scheduling for Faster Builds — uneven action duration makes queue length weak evidence; BuildBuddy describes late binding and exclusive claims as one scheduling design.

  3. Lessons From Routing Remote Actions at Scale — BuildBuddy's pool, executor-local queue, lease, tenant round-robin, and priority-queue implementation.

  4. Post Mortems for 4 Years of Remote Execution — queue-age overload, per-build guardrails, and proxy backpressure consequences in a production remote-execution service. 1 2