Skip to content

Retry remote executions the server fails transiently - #1570

Open
reginaldl wants to merge 3 commits into
meta:mainfrom
reginaldl:reginaldl/retry-transient-execution-failures
Open

reginaldl wants to merge 3 commits into
meta:mainfrom
reginaldl:reginaldl/retry-transient-execution-failures

Conversation

@reginaldl

@reginaldl reginaldl commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Goal

When the server finishes an operation with a transient error, in Operation.error or ExecuteResponse.status, the action fails. Only failures of the gRPC call itself are retried (#1569). A worker-side failure that the server reports this way, such as an I/O error on the worker, fails the build even though running the action again would succeed.

Change

  • UNAVAILABLE, ABORTED, UNKNOWN and INTERNAL on a finished operation are treated as transient. Bazel's GrpcRemoteExecutor likewise retries both Operation.error and ExecuteResponse.status.
  • On such a failure the stream forgets the operation and starts over with a fresh Execute. WaitExecution would only return the same failure.
  • Restarts draw on the same per-action budget, MAX_EXECUTION_RETRIES, with the same backoff, so an action the server keeps failing runs at most six times.
  • The error now names the status, for example Server failed the execution with status Unavailable: …. REClientError on its own prints only the server's message.
  • The budget-and-backoff half of wait_to_retry is split out as back_off, which both paths use. Transport-error classification is unchanged.

Deliberately not retried:

Case Why
Non-zero exit code Not an error: REAPI reports it with an OK status. The command ran.
DEADLINE_EXCEEDED The action's own timeout; running it again would time out again.
RESOURCE_EXHAUSTED Can be the action asking for more than any worker offers; retrying only adds load. Bazel retries it; this does not, on purpose.
Permanent codes (INVALID_ARGUMENT, FAILED_PRECONDITION, …) Resubmitting the same request cannot change the answer.

Testing

The two tests that pinned no-retry on INTERNAL keep their intent but now use inputs that genuinely should not be retried. New tests cover the retried cases, the bound, and the full classification.

  • execution_does_not_retry_a_nonzero_exit_code (new)
  • execution_does_not_retry_a_timed_out_action (was execution_does_not_retry_a_failed_action, now DEADLINE_EXCEEDED)
  • execution_does_not_retry_a_permanent_operation_error (was execution_does_not_retry_an_operation_error, now INVALID_ARGUMENT)
  • execution_does_not_retry_resource_exhaustion (new)
  • execution_restarts_after_a_transient_operation_error (new: UNAVAILABLE in Operation.error, asserts a fresh Execute)
  • execution_restarts_after_an_internal_action_status (new: INTERNAL in ExecuteResponse.status)
  • execution_stops_restarting_once_the_budget_is_spent (new)
  • execution_restarts_only_on_transient_statuses (new: table over all sixteen non-OK codes)

Results:

  • cargo test -p remote_execution --lib: 50 passed.
  • python3 test.py --lint-only remote_execution (clippy with --deny=warnings) and cargo fmt --check: clean.

`execute_with_progress` retried only the opening of the `Execute` call.
Once the stream was open, any error on it, such as a connection reset or a
GOAWAY, failed the action even though the operation was still running on
the server.

Follow Bazel's `GrpcRemoteExecutor`: once the server has named the
operation, reattach to it with `WaitExecution` instead of failing or
re-queueing. `NOT_FOUND` or `UNIMPLEMENTED` while resuming means the server
no longer knows the operation, so start over with `Execute`. A stream that
closes cleanly without a result is resumed without spending the retry
budget, since REAPI allows an execution to outlive a connection, but a
server that keeps resuming without reporting anything is given up on.

Retries are bounded per action (5, matching Bazel's `--remote_retries`
default) and use the same exponential backoff and jitter as `retry`, now
shared between the two. Errors are classified by the existing
`is_retryable`; `DEADLINE_EXCEEDED` is additionally retried while resuming,
but not on a fresh `Execute`, where REAPI defines it as the action's own
timeout.
A finished operation that carries a transient failure, in `Operation.error`
or `ExecuteResponse.status`, was surfaced as a terminal error. Only failures
of the gRPC call itself were retried, so a worker-side problem the server
reported this way failed the build.

Treat `UNAVAILABLE`, `ABORTED`, `UNKNOWN` and `INTERNAL` on a finished
operation as transient, as Bazel's `GrpcRemoteExecutor` does: forget the
operation and start over with a fresh `Execute`, since `WaitExecution` would
only return the same failure. Restarts draw on the existing per-action budget
(`MAX_EXECUTION_RETRIES`), so an action runs at most one more time than that.
The error now names the status, which `REClientError` alone does not print.

`DEADLINE_EXCEEDED` (the action's own timeout) and `RESOURCE_EXHAUSTED` (may
be the action asking for more than any worker offers) are still not retried,
nor are permanent codes. A command that exits non-zero is unaffected: REAPI
reports it with an `OK` status.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 13, 2026
@meta-codesync

meta-codesync Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D125105000. (Because this pull request was imported automatically, there will not be any future comments.)

@8Keep

8Keep commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Seems good to me. But this one needs a rebase

@scottcao scottcao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants