Skip to content

fix(bazel): identify bazel query context timeouts via sentinel error - #246

Open
yushan8 wants to merge 6 commits into
mainfrom
yushan/bazel-query-context-timeout
Open

fix(bazel): identify bazel query context timeouts via sentinel error#246
yushan8 wants to merge 6 commits into
mainfrom
yushan/bazel-query-context-timeout

Conversation

@yushan8

@yushan8 yushan8 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Intent:

  • A bazel query killed for exceeding its configured timeout was previously
    wrapped as a plain error with no way to distinguish it from any other
    compute failure. Services consuming Tango need to be able to tell a
    service-side query timeout (the query's own context, not a parent
    cancellation, hit its deadline) apart from a client disconnecting, so
    they can handle/log it distinctly.

Changes:

  • Add bazel.ErrQueryTimeout, wrapped in executeQueryInternal when the
    query's own context (not a parent cancellation) has hit its deadline —
    whether that surfaces via cmd.Wait() failing or via the stream-reading
    goroutines being canceled mid-read.
  • The sentinel is identification-only: it is reachable via errors.Is
    through the %w chain, but GetTargetGraph does not classify it as
    retryable — it falls through to the default ErrorInfra like any other
    compute failure.

Follow-up to #236, which classified bazelisk download network failures as
retryable (a different case: that one is a genuine transient failure
before any query even starts).

Test Plan

  • Added TestExecuteQueryInternal_ContextTimeout assertion that the error
    now wraps bazel.ErrQueryTimeout.
  • Added TestExecuteQueryInternal_WaitFailureWithoutTimeout to confirm a
    plain wait failure (no context deadline) is not misclassified as a
    timeout.
  • Added TestExecuteQueryInternal_StreamTimeoutWithoutWaitError to confirm
    a timeout surfacing only via the stream-reading goroutines is still
    wrapped with ErrQueryTimeout.

Revert Plan

Revert this PR; the sentinel and its detection are removed, with no
change to runtime classification behavior.

Intent:
- A bazel query killed for exceeding its configured timeout was
  previously wrapped as a plain error, falling through to the
  ErrorInfra default in tangoerrors.GetErrorCode. A query timeout can
  be transient (a loaded machine, a cold repo cache) and is worth
  retrying, unlike a genuine query/config failure.

Changes:
- Add bazel.ErrQueryTimeout, wrapped in executeQueryInternal when
  cmd.Wait() fails and the query's own context (not a parent
  cancellation) has hit its deadline.
- Add orchestrator.classifyComputeError, classifying ErrQueryTimeout as
  tangoerrors.ErrorInfraRetryable and everything else as
  tangoerrors.ErrorInfra; wired into nativeOrchestrator's compute step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@yushan8
yushan8 requested review from a team as code owners July 28, 2026 18:22
waitErr and streamErr are two independent consumers of cmdCtx: if the
bazel process exits cleanly right at the deadline, waitErr can be nil
while the stream-reading goroutines are still canceled mid-read via
gCtx, surfacing as streamErr instead. That path wasn't wrapped with
ErrQueryTimeout, so it fell through to the non-retryable default even
though it's the same timeout condition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@yushan8
yushan8 marked this pull request as draft July 28, 2026 18:37
yushan8 and others added 2 commits July 28, 2026 11:41
bazel.ErrQueryTimeout exists to let callers distinguish a service-side
query timeout (ctx.Err() == DeadlineExceeded on the query's own
timeout) from a client-initiated cancellation via errors.Is, not to
drive automatic retries. Drop classifyComputeError and go back to
plain error wrapping in GetTargetGraph; the sentinel is still
reachable through the %w chain from core/bazel/query.go.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Update the doc comment left over from when this sentinel drove
retryable classification; it now exists purely so callers can tell a
service-side query timeout apart from a client cancellation via
errors.Is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@yushan8 yushan8 changed the title fix(bazel): classify bazel query context timeouts as retryable fix(bazel): identify bazel query context timeouts via sentinel error Jul 28, 2026
@yushan8
yushan8 marked this pull request as ready for review July 28, 2026 22:52
yushan8 and others added 2 commits July 28, 2026 15:57
Both call sites needed the same two-line "if timedOut { wrap with
ErrQueryTimeout }" before calling wrapQueryFailure. Pass timedOut
through instead so the wrapping happens once, in one place.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Let wrapQueryFailure check ctx.Err() == context.DeadlineExceeded itself
rather than having the caller precompute a timedOut bool and thread it
through.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread core/bazel/query.go
// live, so it's omitted.
func (b *BazelClient) wrapQueryFailure(msg string, cause error, stderrBuf *bytes.Buffer) error {
func (b *BazelClient) wrapQueryFailure(ctx context.Context, msg string, cause error, stderrBuf *bytes.Buffer) error {
if ctx.Err() == context.DeadlineExceeded {

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.

we should do this check, but a sentinel seems overkill since we're not handling it anywhere. A plain fmt.Errorf achieves the same result. Unless we plan to do something with it right now, I'd prefer just making it a plain error until we specifically want to handle this.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants