Fix ContextCanceled: context canceled in Go
This error occurs when an operation's context is canceled, typically because the HTTP client disconnected, a parent context timed out, or cancel() was called explicitly. Fix it by checking for context cancellation errors and handling them gracefully instead of treating them as unexpected failures. Log at debug level for client disconnects.
Reading the Stack Trace
Here's what each line means:
- main.fetchData(0x14000226000) /app/services/data.go:28 +0x2d8: The fetchData function at line 28 encounters a canceled context, likely because the HTTP client disconnected.
- context.(*cancelCtx).Err(0x14000196040): The context's Err method returns Canceled, indicating the context was explicitly canceled by a parent.
- database/sql.(*DB).QueryContext(0x14000118300, {0x1029f0ea0, 0x14000196040}, {0x1028f1e60, 0x20}, {0x0, 0x0, 0x0}): The database query is canceled mid-flight because the context it depends on was canceled.
Common Causes
1. Client disconnected during request
The HTTP client closed the connection (browser tab closed, timeout), canceling the request context.
func fetchData(c *gin.Context) {
rows, err := db.QueryContext(c.Request.Context(), longQuery)
if err != nil {
log.Printf("ERROR: %v", err) // logs client disconnects as errors
c.JSON(500, gin.H{"error": "database error"})
return
}
}
2. Parent context canceled too early
A parent context is canceled before child operations complete, cascading cancellation to all derived contexts.
func handler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
cancel() // called immediately — all operations using ctx are canceled
result, err := slowOperation(ctx)
}
3. cancel() called in defer before result used
Using defer cancel() correctly, but the error is not distinguished from other failures.
func getData(ctx context.Context) ([]byte, error) {
resp, err := http.Get(url) // does not use ctx
if err != nil {
return nil, err // could be context.Canceled — not checked
}
return io.ReadAll(resp.Body)
}
The Fix
Check for context.Canceled and context.DeadlineExceeded separately. Client disconnects (Canceled) should be logged at debug level and not trigger error alerts. Deadline exceeded should return 504. Only log unexpected database errors as errors.
func fetchData(c *gin.Context) {
rows, err := db.QueryContext(c.Request.Context(), longQuery)
if err != nil {
log.Printf("ERROR: %v", err)
c.JSON(500, gin.H{"error": "database error"})
return
}
defer rows.Close()
// process rows...
}
func fetchData(c *gin.Context) {
rows, err := db.QueryContext(c.Request.Context(), longQuery)
if err != nil {
if errors.Is(err, context.Canceled) {
log.Printf("DEBUG: client disconnected during query")
return // client is gone, no response needed
}
if errors.Is(err, context.DeadlineExceeded) {
c.JSON(http.StatusGatewayTimeout, gin.H{"error": "query timed out"})
return
}
log.Printf("ERROR: database query failed: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
return
}
defer rows.Close()
// process rows...
}
Testing the Fix
package services_test
import (
"context"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestFetchData_ContextCanceled(t *testing.T) {
db, mock, _ := sqlmock.New()
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnError(context.Canceled)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := fetchDataWithContext(ctx, db)
assert.ErrorIs(t, err, context.Canceled)
}
func TestFetchData_DeadlineExceeded(t *testing.T) {
db, mock, _ := sqlmock.New()
defer db.Close()
mock.ExpectQuery("SELECT").WillReturnError(context.DeadlineExceeded)
_, err := fetchDataWithContext(context.Background(), db)
assert.ErrorIs(t, err, context.DeadlineExceeded)
}
func TestFetchData_Success(t *testing.T) {
db, mock, _ := sqlmock.New()
defer db.Close()
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
result, err := fetchDataWithContext(context.Background(), db)
assert.NoError(t, err)
assert.NotNil(t, result)
}
Run your tests:
go test ./services/... -v
Pushing Through CI/CD
git checkout -b fix/go-context-canceled,git add services/data.go services/data_test.go,git commit -m "fix: distinguish context cancellation from database errors",git push origin fix/go-context-canceled
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go mod download
- run: go vet ./...
- run: go test ./... -race -coverprofile=coverage.out
- run: go build ./...
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
go get github.com/bugstack/sdk
Step 2: Initialize
import "github.com/bugstack/sdk"
func init() {
bugstack.Init(os.Getenv("BUGSTACK_API_KEY"))
}
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run go test ./... locally to confirm context handling works.
- Open a pull request with the context error handling changes.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and verify in staging.
Frequently Asked Questions
BugStack tests with simulated context cancellation and deadline scenarios, validates error classification logic, and ensures no false error alerts are triggered before marking it safe to deploy.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
These are usually normal — clients close browsers, mobile apps background, and load balancers drop idle connections. Log them at debug level, not error.
Canceled means cancel() was called explicitly (often client disconnect). DeadlineExceeded means the context's timeout or deadline was reached.