Skip to content

experimental/ssh: retry tunnel binary upload on transient errors#5836

Open
TanishqDatabricks wants to merge 1 commit into
mainfrom
ssh-retry-binary-upload
Open

experimental/ssh: retry tunnel binary upload on transient errors#5836
TanishqDatabricks wants to merge 1 commit into
mainfrom
ssh-retry-binary-upload

Conversation

@TanishqDatabricks

Copy link
Copy Markdown
Collaborator

Changes

The binary-upload phase of databricks ssh connect (UploadTunnelReleases) did a single Stat followed by a single Write against the workspace-files API per architecture. A single transient failure on either call aborted the whole connect.

In practice this fires often on a busy workspace:

  • A cold get-status request routinely takes ~60s — right at the SDK's per-request inactivity timeout — so the existence check trips request timed out after 1m0s of inactivity and aborts. Repeating the same stat immediately after is sub-second (the endpoint is warm).
  • The import (Write) endpoint intermittently returns a 5xx Internal error.

This wraps both operations in a bounded retry (5 min) that re-runs only on transient errors:

  • SDK-classified retriable API errors (apierr.APIError.IsRetriable — 429, 503, etc.), and
  • request timeouts (context.DeadlineExceeded / os.ErrDeadlineExceeded, plus the SDK's stringified "request timed out after ...").

Not retried:

  • fs.ErrNotExist from Stat — that's the signal that the binary isn't there yet and must be uploaded; it resolves immediately.
  • Stream resets — a proxy body-size rejection where retrying won't help; these still fail fast with the existing actionable hint about egress proxies.

The upload retry re-fetches the release reader on each attempt, since Write drains it.

Why

Transient workspace-files blips (cold get-status latency, intermittent 5xx) were the most common real-world failure when connecting: a single 60s stat timeout or one 5xx would kill an otherwise-healthy connect, and the fix was always just "run it again." A bounded retry absorbs these transparently.

Tests

New unit tests in releases_test.go using a scripted stub filer:

  • TestIsRetriableUploadError — timeout string, context.DeadlineExceeded, retriable 503/429, non-retriable 404, plain error.
  • TestBinaryExistsRetriesTransientStat — transient stat timeout then success.
  • TestBinaryExistsNotFoundNoRetryfs.ErrNotExist resolves in one call (no retry).
  • TestBinaryExistsHaltsOnNonRetriable — a 403 halts immediately.
  • TestUploadReleaseRetriesTransientWrite — transient 503 then success, re-fetching a fresh reader.
  • TestUploadReleaseStreamResetNoRetry — stream reset fails fast with the proxy hint, no retry.

go test ./experimental/ssh/... passes; gofmt and go vet clean.

Notes

This is an independent robustness fix, separate from the --base-environment env-version work. It targets only the tunnel binary-upload path; the server-side metadata.json write (which can hit the same transient 5xx) is a candidate for the same treatment in a follow-up.

This pull request and its description were written by Isaac.

The binary-upload phase of ssh connect did a single Stat and single Write
against the workspace-files API, so one transient failure aborted the whole
connect. In practice this fires often: a cold get-status request routinely
takes ~60s (right at the SDK's per-request inactivity timeout), and the import
endpoint intermittently returns 5xx.

Wrap the stat and the upload in a bounded retry (5 min) that re-runs on
transient errors — SDK-classified retriable API errors (429/503/...) and
request timeouts. The reader is re-fetched each attempt since Write drains it.
fs.ErrNotExist (the signal to upload) and stream resets (a proxy body-size
rejection, where retrying won't help) are not retried.

Co-authored-by: Isaac
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

Based on git history, these people are best suited to review:

  • @denik -- recent work in ./
  • @anton-107 -- recent work in ./, experimental/ssh/internal/client/
  • @janniklasrose -- recent work in ./

Eligible reviewers: @andrewnester, @pietern, @renaudhartert-db, @shreyas-goenka, @simonfaltum

Suggestions based on git history. See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 33cd676

Run: 28780131617

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 15 230 1046 3:53
💚​ aws windows 4 15 232 1044 4:03
💚​ aws-ucws linux 4 15 314 964 4:49
💚​ aws-ucws windows 4 15 316 962 4:08
🔄​ azure linux 2 2 15 230 1045 5:37
💚​ azure windows 4 15 232 1043 3:46
💚​ azure-ucws linux 4 15 316 961 5:32
💚​ azure-ucws windows 4 15 318 959 4:05
💚​ gcp linux 4 15 229 1047 3:48
💚​ gcp windows 4 15 231 1045 3:44
19 interesting tests: 15 SKIP, 2 RECOVERED, 2 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/replace_existing 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🔄​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 💚​R 💚​R
🔄​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 💚​R 💚​R
Top 5 slowest tests (at least 2 minutes):
duration env testname
3:17 aws-ucws windows TestAccept
3:15 aws windows TestAccept
3:07 azure-ucws windows TestAccept
2:56 gcp windows TestAccept
2:54 azure windows TestAccept

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