Repository navigation
Retry the deploy lock create after transient errors #7594
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
denik
wants to merge
9
commits into
main
Choose a base branch
from
denik/retry-lock-create
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+306
−20
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
9935ba0
Retry the deploy lock create after transient errors
denik 315bdd0
Link the changelog entry to the PR
denik 2d04b74
Read the lock back when the last create attempt fails transiently
denik e461f63
acc: keep the lock-retry workspace path intact on Windows
denik 53eb056
Also retry the deploy lock create after unexpected EOF
denik aafff5e
Name the lock holder after a transient lock create, test forced locks
denik bd7f041
Keep the write error for a forced lock that fails transiently
denik b51ca4d
Say which errors the forced lock write is retried on
denik f440de2
acc: commit the other deployer's lock file in lock-retry
denik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| * Retry creating the deployment lock (without `--force-lock`) when the request fails with a transient server error, times out ("request timed out after ... of inactivity") or loses its connection ("unexpected EOF"). ([#7594](https://github.com/databricks/cli/pull/7594)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| bundle: | ||
| name: test-bundle |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"ID":"00000000-0000-0000-0000-000000000000","AcquisitionTime":"2026-01-01T00:00:00Z","IsForced":false,"User":"someone-else"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Cloud = false | ||
| EnvMatrix.DMS = ["", "true"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
|
|
||
| === Lock create is applied, but the response is a 504: the retry finds our own lock | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Files: 0 uploaded, 0 deleted | ||
| Resources: 0 created, 0 changed, 0 deleted, 0 unchanged | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
|
|
||
| === Lock create is not applied and fails with a 504: the retry creates the lock | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Files: 0 uploaded, 0 deleted | ||
| Resources: 0 created, 0 changed, 0 deleted, 0 unchanged | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
|
|
||
| === Every attempt returns a 504, but the first one is applied: reading the lock back finds it is ours | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Files: 0 uploaded, 0 deleted | ||
| Resources: 0 created, 0 changed, 0 deleted, 0 unchanged | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
|
|
||
| === Lock is held by another deployer: the retry gets a conflict and the error names the holder | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Error: Failed to acquire deployment lock: deploy lock acquired by someone-else at [TIMESTAMP] +0000 UTC. | ||
| Another deployment may be in progress. Use --force-lock to override, but this may | ||
| conflict with the other deployment if it is still active | ||
| Error: deploy lock acquired by someone-else at [TIMESTAMP] +0000 UTC. | ||
| Another deployment may be in progress. Use --force-lock to override, but this may | ||
| conflict with the other deployment if it is still active | ||
|
|
||
|
|
||
| Exit code: 1 | ||
|
|
||
| === Lock is held by another deployer and every attempt returns a 504: reading the lock back names the holder | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Error: Failed to acquire deployment lock: deploy lock acquired by someone-else at [TIMESTAMP] +0000 UTC. | ||
| Another deployment may be in progress. Use --force-lock to override, but this may | ||
| conflict with the other deployment if it is still active | ||
| Error: deploy lock acquired by someone-else at [TIMESTAMP] +0000 UTC. | ||
| Another deployment may be in progress. Use --force-lock to override, but this may | ||
| conflict with the other deployment if it is still active | ||
|
|
||
|
|
||
| Exit code: 1 | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
| ["POST","false"] | ||
|
|
||
| === Forced lock, every overwrite attempt returns a 504 but is applied: reading the lock back finds it is ours | ||
|
|
||
| >>> [CLI] bundle deploy --force-lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Files: 0 uploaded, 0 deleted | ||
| Resources: 0 created, 0 changed, 0 deleted, 0 unchanged | ||
| ["POST","true"] | ||
| ["POST","true"] | ||
| ["POST","true"] | ||
|
|
||
| === Forced lock over another deployer's lock, every overwrite attempt returns a 504 and is not applied: the error is the 504 | ||
|
|
||
| >>> [CLI] bundle deploy --force-lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Warn: retrying after 504 Gateway Timeout from POST /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| Error: Failed to acquire deployment lock: Fault injected by test. | ||
| Error: Fault injected by test. (504 INJECTED) | ||
|
|
||
| Endpoint: POST [DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Fstate%2Fdeploy.lock?overwrite=true | ||
| HTTP Status: 504 Gateway Timeout | ||
| API error_code: INJECTED | ||
| API message: Fault injected by test. | ||
|
|
||
|
|
||
| Exit code: 1 | ||
| ["POST","true"] | ||
| ["POST","true"] | ||
| ["POST","true"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Deploy once so that the state directory exists and the lock create is a single request. | ||
| $CLI bundle deploy > /dev/null 2>&1 | ||
| print_requests.py > /dev/null | ||
|
|
||
| title "Lock create is applied, but the response is a 504: the retry finds our own lock" | ||
| fault.py --after-handler "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 1 | ||
| trace $CLI bundle deploy | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' | ||
|
|
||
| title "Lock create is not applied and fails with a 504: the retry creates the lock" | ||
| fault.py "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 1 | ||
| trace $CLI bundle deploy | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' | ||
|
|
||
| title "Every attempt returns a 504, but the first one is applied: reading the lock back finds it is ours" | ||
| fault.py --after-handler "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 3 | ||
| trace $CLI bundle deploy | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' | ||
|
|
||
| title "Lock is held by another deployer: the retry gets a conflict and the error names the holder" | ||
| fault.py "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 1 | ||
| # other.lock is a lock held by another deployer. | ||
| # MSYS_NO_PATHCONV stops Git Bash on Windows from turning the workspace path into a local one. | ||
| MSYS_NO_PATHCONV=1 $CLI workspace import /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle/default/state/deploy.lock --file other.lock --format AUTO > /dev/null | ||
| errcode trace $CLI bundle deploy | ||
| print_requests.py > /dev/null | ||
|
|
||
| title "Lock is held by another deployer and every attempt returns a 504: reading the lock back names the holder" | ||
| fault.py --after-handler "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 3 | ||
| errcode trace $CLI bundle deploy | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' | ||
|
|
||
| title "Forced lock, every overwrite attempt returns a 504 but is applied: reading the lock back finds it is ours" | ||
| fault.py --after-handler "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 3 | ||
| trace $CLI bundle deploy --force-lock | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' | ||
|
|
||
| title "Forced lock over another deployer's lock, every overwrite attempt returns a 504 and is not applied: the error is the 504" | ||
| MSYS_NO_PATHCONV=1 $CLI workspace import /Workspace/Users/$CURRENT_USER_NAME/.bundle/test-bundle/default/state/deploy.lock --file other.lock --format AUTO --overwrite > /dev/null | ||
| print_requests.py > /dev/null | ||
| fault.py "POST /api/2.0/workspace-files/import-file/**/deploy.lock" 504 0 3 | ||
| errcode trace $CLI bundle deploy --force-lock | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -c 'select(.path | contains("deploy.lock")) | [.method, .q.overwrite]' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| RecordRequests = true | ||
| Env.DATABRICKS_BUNDLE_RETRY_INTERVAL_MS = "100" | ||
| SuppressLines.warn_retry = "" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| bundle: | ||
| name: test-bundle |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Cloud = false | ||
| EnvMatrix.DMS = ["", "true"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| Warn: retrying after Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Fstate%2Fdeploy.lock?overwrite=false": request timed out after 5s of inactivity | ||
| Warn: retrying after Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Fstate%2Fdeploy.lock?overwrite=false": request timed out after 5s of inactivity | ||
| Error: Failed to acquire deployment lock: Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Fstate%2Fdeploy.lock?overwrite=false": request timed out after 5s of inactivity | ||
| Error: Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Fstate%2Fdeploy.lock?overwrite=false": request timed out after 5s of inactivity | ||
|
|
||
| /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock | ||
| /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| musterr $CLI bundle deploy | ||
|
|
||
| # Three attempts: the first one and two retries. | ||
| print_requests.py //api/2.0/workspace-files/import-file | jq -r .path | grep 'deploy.lock' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| Env.DATABRICKS_BUNDLE_HTTP_TIMEOUT_SECONDS = "5" | ||
| Env.DATABRICKS_BUNDLE_RETRY_INTERVAL_MS = "100" | ||
| SuppressLines.warn_retry = "" | ||
| RecordRequests = true | ||
|
|
||
| [[Server]] | ||
| # Every lock create attempt outlives the 5s inactivity timeout, so the CLI retries it twice, | ||
| # finds no lock when it reads it back, and fails with the timeout. | ||
| Delay = "30s" | ||
| Pattern = "POST /api/2.0/workspace-files/import-file/Workspace/Users/tester@databricks.com/.bundle/test-bundle/default/state/deploy.lock" | ||
| Response.StatusCode = 200 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package locker | ||
|
|
||
| import ( | ||
| "context" | ||
| "io" | ||
| "net/url" | ||
| "testing" | ||
| "uuid" | ||
|
|
||
| "github.com/databricks/cli/libs/env" | ||
| "github.com/databricks/cli/libs/filer" | ||
| "github.com/databricks/cli/libs/retry" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // eofFiler fails the first `failures` writes with the error net/http returns when the connection | ||
| // is cut mid-response. The write numbered appliedOn (1-based, 0 for none) still writes the file | ||
| // before failing, like a request the server applied before the connection was cut. | ||
| type eofFiler struct { | ||
| filer.Filer | ||
| failures int | ||
| appliedOn int | ||
| writes int | ||
| } | ||
|
|
||
| func (f *eofFiler) Write(ctx context.Context, path string, reader io.Reader, mode ...filer.WriteMode) error { | ||
| f.writes++ | ||
| if f.writes > f.failures { | ||
| return f.Filer.Write(ctx, path, reader, mode...) | ||
| } | ||
| if f.writes == f.appliedOn { | ||
| if err := f.Filer.Write(ctx, path, reader, mode...); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return &url.Error{Op: "Post", URL: "https://example.com/" + path, Err: io.ErrUnexpectedEOF} | ||
| } | ||
|
|
||
| func newTestLocker(t *testing.T, f filer.Filer) *Locker { | ||
| return &Locker{ | ||
| filer: f, | ||
| TargetDir: t.TempDir(), | ||
| State: &LockState{ID: uuid.New(), User: "tester"}, | ||
| } | ||
| } | ||
|
|
||
| func TestLockRetriesUnexpectedEOF(t *testing.T) { | ||
| ctx := env.Set(t.Context(), retry.IntervalMsVariable, "1") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| failures int | ||
| appliedOn int | ||
| }{ | ||
| // The retry creates the lock. | ||
| {name: "not applied", failures: 1, appliedOn: 0}, | ||
| // The retry fails with "already exists" and the read-back finds our lock. | ||
| {name: "applied", failures: 1, appliedOn: 1}, | ||
| // Retries run out, but the last attempt was applied: the read-back finds our lock. | ||
| {name: "last attempt applied", failures: 3, appliedOn: 3}, | ||
| } | ||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| local, err := filer.NewLocalClient(t.TempDir()) | ||
| require.NoError(t, err) | ||
| f := &eofFiler{Filer: local, failures: tc.failures, appliedOn: tc.appliedOn} | ||
| locker := newTestLocker(t, f) | ||
|
|
||
| require.NoError(t, locker.Lock(ctx, false)) | ||
| assert.True(t, locker.Active) | ||
| assert.Equal(t, min(tc.failures+1, 3), f.writes) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestLockFailsWhenEveryAttemptHitsUnexpectedEOF(t *testing.T) { | ||
| ctx := env.Set(t.Context(), retry.IntervalMsVariable, "1") | ||
| local, err := filer.NewLocalClient(t.TempDir()) | ||
| require.NoError(t, err) | ||
| f := &eofFiler{Filer: local, failures: 3} | ||
| locker := newTestLocker(t, f) | ||
|
|
||
| err = locker.Lock(ctx, false) | ||
| require.ErrorIs(t, err, io.ErrUnexpectedEOF) | ||
| assert.False(t, locker.Active) | ||
| assert.Equal(t, 3, f.writes) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.