-
Notifications
You must be signed in to change notification settings - Fork 461
fix: WrapContextAsUserError should not misclassify internal timeouts as user cancellations #3155
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
Merged
dobrac
merged 4 commits into
e2b-dev:main
from
AdaAibaby:fix/wrap-context-user-error-misclassification
Jul 1, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
dfb7038
fix: WrapContextAsUserError should not misclassify internal timeouts …
adababys 0293212
fix: add double check for context error in WrapContextAsUserError
adababys ff24792
fix: address golangci-lint errors in tests
adababys 3a829f9
Merge branch 'main' into fix/wrap-context-user-error-misclassification
AdaAibaby 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
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
138 changes: 138 additions & 0 deletions
138
packages/orchestrator/pkg/template/build/builderrors/errors_test.go
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,138 @@ | ||
| //go:build linux | ||
|
|
||
| package builderrors | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/e2b-dev/infra/packages/orchestrator/pkg/template/build/phases" | ||
| ) | ||
|
|
||
| func TestWrapContextAsUserError_NilError(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| ctx := context.Background() | ||
| if got := WrapContextAsUserError(ctx, nil); got != nil { | ||
| t.Errorf("expected nil, got %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_AlreadyUserError(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| ctx := context.Background() | ||
| userErr := phases.NewPhaseBuildError(phases.PhaseMeta{}, errors.New("user mistake")) | ||
| got := WrapContextAsUserError(ctx, userErr) | ||
| if !errors.Is(got, userErr) { | ||
| t.Errorf("expected original user error returned as-is, got %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_InternalTimeout_NotMisclassified(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // Simulate: build context is still active, but error contains context.Canceled | ||
| // from an internal child-context timeout (e.g., WaitForEnvd). | ||
| buildCtx := context.Background() // not canceled | ||
|
|
||
| // This is what doRequestWithInfiniteRetries returns when a child context is canceled | ||
| internalErr := fmt.Errorf("%w with cause: %w", context.Canceled, errors.New("syncing took too long")) | ||
|
|
||
| got := WrapContextAsUserError(buildCtx, internalErr) | ||
|
|
||
| // Should NOT be classified as user error — the build context is still alive | ||
| if IsUserError(got) { | ||
| t.Errorf("internal timeout should not be classified as user error, got: %v", got) | ||
| } | ||
| // Should preserve the original error | ||
| if !errors.Is(got, internalErr) { | ||
| t.Errorf("expected original error preserved, got: %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_UserCancellation(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // Simulate: user cancels the build → build context is canceled | ||
| buildCtx, cancel := context.WithCancel(context.Background()) | ||
| cancel() // user canceled | ||
|
|
||
| // Error must contain context.Canceled (as it would in real code via errors.Join or wrapping) | ||
| someErr := fmt.Errorf("some operation failed: %w", buildCtx.Err()) | ||
| got := WrapContextAsUserError(buildCtx, someErr) | ||
|
|
||
| if !IsUserError(got) { | ||
| t.Errorf("expected user error when build context is canceled, got: %v", got) | ||
| } | ||
| if !errors.Is(got, ErrCanceled) { | ||
| t.Errorf("expected ErrCanceled, got: %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_BuildContextCanceled_ErrorUnrelated(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // Build context is canceled, but the error is unrelated (e.g., disk error). | ||
| // Should NOT be wrapped as user error. | ||
| buildCtx, cancel := context.WithCancel(context.Background()) | ||
| cancel() | ||
|
|
||
| diskErr := errors.New("disk I/O error") | ||
| got := WrapContextAsUserError(buildCtx, diskErr) | ||
|
|
||
| if IsUserError(got) { | ||
| t.Errorf("unrelated error should not be classified as user error when build context is canceled, got: %v", got) | ||
| } | ||
| if !errors.Is(got, diskErr) { | ||
| t.Errorf("expected original error preserved, got: %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_BuildDeadlineExceeded(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // Simulate: build context deadline exceeded | ||
| buildCtx, cancel := context.WithTimeout(context.Background(), 0) | ||
| defer cancel() | ||
| <-buildCtx.Done() // ensure it's expired | ||
|
|
||
| // Error must contain context.DeadlineExceeded (as it would in real code) | ||
| someErr := fmt.Errorf("some operation failed: %w", buildCtx.Err()) | ||
| got := WrapContextAsUserError(buildCtx, someErr) | ||
|
|
||
| if !IsUserError(got) { | ||
| t.Errorf("expected user error when build context timed out, got: %v", got) | ||
| } | ||
| if !errors.Is(got, ErrTimeout) { | ||
| t.Errorf("expected ErrTimeout, got: %v", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWrapContextAsUserError_InternalCanceledError_BuildContextAlive(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // The key regression test: error chain contains context.Canceled from a child | ||
| // context, but the build context is NOT canceled. Must NOT be treated as user error. | ||
| buildCtx := context.Background() | ||
|
|
||
| // Simulate WaitForEnvd timeout chain: | ||
| // child context canceled → doRequestWithInfiniteRetries wraps ctx.Err() | ||
| childCtx, childCancel := context.WithCancelCause(buildCtx) | ||
| childCancel(errors.New("syncing took too long")) | ||
|
|
||
| err := fmt.Errorf("failed to init new envd: %w", | ||
| fmt.Errorf("%w with cause: %w", childCtx.Err(), context.Cause(childCtx))) | ||
|
|
||
| got := WrapContextAsUserError(buildCtx, err) | ||
|
|
||
| if IsUserError(got) { | ||
| t.Errorf("internal child-context cancellation must not be classified as user error, got: %v", got) | ||
| } | ||
| // The original error should be preserved | ||
| if !errors.Is(got, context.Canceled) { | ||
| t.Errorf("original error chain should be preserved, got: %v", got) | ||
| } | ||
| } |
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.