Skip to content

fix: context.Canceled propagated as non-retryable in execute() causing flaky TestBasicExampleQuery/ExecuteDataQuery#2115

Draft
asmyasnikov with Copilot wants to merge 8 commits into
masterfrom
copilot/fix-flaky-test-basic-example-query
Draft

fix: context.Canceled propagated as non-retryable in execute() causing flaky TestBasicExampleQuery/ExecuteDataQuery#2115
asmyasnikov with Copilot wants to merge 8 commits into
masterfrom
copilot/fix-flaky-test-basic-example-query

Conversation

Copilot AI commented May 3, 2026

Copy link
Copy Markdown
Contributor

Two race windows in execute() where context.Canceled was returned as non-retryable, causing pool.With to fail rather than retry with a fresh session.

Race 1 — c.ExecuteQuery fails before stream opens: Session.execute merges user context with session lifetime via xcontext.WithDone(ctx, s.Done()). When a session dies, this merged ctx is immediately cancelled. AfterFunc(ctx, executeCancel) then fires before c.ExecuteQuery runs, the balancer's nextConn sees executeCtx.Err() != nil and returns context.Canceled, which was propagated non-retryably at execute_query.go:140.

Race 2 — newResult fails after stream opens: After the non-blocking ctx.Done() check passes, the session can die, firing AfterFunc(ctx, executeCancel) which cancels executeCtx. The subsequent newResult(executeCtx, ...) then fails with context.Canceled, which was also returned as non-retryable.

Changes

  • internal/query/execute_query.go:

    • In the c.ExecuteQuery error path: added select { case <-ctx.Done(): return retryable ... } check (same pattern as the existing check that follows it), so that if ctx is done when ExecuteQuery fails, a retryable error is returned.
    • After newResult returns an error: check xerrors.Is(err, context.Canceled). If true, wrap as xerrors.Retryable(executeCtx.Err(), ...) (falling back to err if executeCtx.Err() is nil). executeCtx.Err() is used instead of ctx.Err() because executeCtx is a separate derived context — err can be context.Canceled even when ctx.Err() is still nil (e.g. session death without user cancellation).

    The outer retry loop runs on the user's context, so if only the session died the retry proceeds; if the user's ctx is also done the loop stops naturally.

  • internal/query/execute_query_test.go:

    • CancelWhileExecute — now additionally asserts xerrors.IsRetryableError(err).
    • SessionDiesBeforeFirstRecv_ParentCtxAlive — new test: Recv() returns context.Canceled while the parent ctx is still alive, asserting ctx.Err() == nil and the error is retryable.
    • SessionDiesBeforeExecuteQuery — new test: directly reproduces the CI failure where ctx is cancelled before ExecuteQuery is called, asserting the returned error is retryable.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown

summary

Inferred base version: v3.135.10
Suggested version: v3.135.11

@codecov-commenter

codecov-commenter commented May 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.06%. Comparing base (9955e4c) to head (9f0e831).
⚠️ Report is 27 commits behind head on master.

Files with missing lines Patch % Lines
internal/query/execute_query.go 87.50% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2115      +/-   ##
==========================================
- Coverage   75.16%   75.06%   -0.11%     
==========================================
  Files         427      427              
  Lines       36974    36989      +15     
==========================================
- Hits        27792    27764      -28     
- Misses       7993     8026      +33     
- Partials     1189     1199      +10     
Flag Coverage Δ
experiment 74.94% <87.50%> (-0.01%) ⬇️
go-1.21.x 72.55% <87.50%> (-0.12%) ⬇️
go-1.26.x 75.05% <87.50%> (-0.07%) ⬇️
integration 57.14% <75.00%> (-0.24%) ⬇️
macOS 46.22% <87.50%> (+<0.01%) ⬆️
ubuntu 75.06% <87.50%> (-0.11%) ⬇️
unit 46.23% <87.50%> (-0.01%) ⬇️
windows 46.21% <87.50%> (-0.01%) ⬇️
ydb-24.4 56.47% <0.00%> (-0.48%) ⬇️
ydb-edge 56.96% <75.00%> (-0.21%) ⬇️
ydb-latest 56.66% <0.00%> (-0.41%) ⬇️
ydb-nightly 74.94% <87.50%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…estBasicExampleQuery/ExecuteDataQuery

When a session dies between the ctx.Done() check and the newResult() call
in execute(), the executeCtx gets cancelled via AfterFunc, causing
newResult to return context.Canceled. Previously this was propagated as
a non-retryable error. Now we detect this pattern (error is context.Canceled)
and wrap it as retryable so the pool can retry with a fresh session.

Agent-Logs-Url: https://github.com/ydb-platform/ydb-go-sdk/sessions/f82324d6-e778-432b-a6f2-6a3f95686166

Co-authored-by: asmyasnikov <14202262+asmyasnikov@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix flaky TestBasicExampleQuery ExecuteDataQuery issue fix: context.Canceled from newResult propagated as non-retryable causing flaky TestBasicExampleQuery/ExecuteDataQuery May 3, 2026
Copilot AI requested a review from asmyasnikov May 3, 2026 13:15
@asmyasnikov
asmyasnikov requested a review from Copilot May 3, 2026 16:08
@github-actions github-actions Bot removed the SLO label May 3, 2026

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR aims to make execute() treat a context.Canceled returned from newResult() as retryable, addressing a race where session cancellation between the ctx.Done() check and the first Recv() caused flaky query failures instead of a retry.

Changes:

  • Update internal/query/execute_query.go to wrap context.Canceled from newResult() as a retryable error.
  • Extend CancelWhileExecute in internal/query/execute_query_test.go to assert the returned error is marked retryable.
  • Add a changelog entry describing the retryability fix for query operations.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
internal/query/execute_query.go Adds retryable wrapping for context.Canceled returned after newResult().
internal/query/execute_query_test.go Updates a cancellation test to assert retryable error classification.
CHANGELOG.md Documents the race-condition fix in the changelog.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/query/execute_query.go Outdated
Comment thread internal/query/execute_query_test.go Outdated

@robot-vibe-db robot-vibe-db Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review Summary

Verdict: ✅ No critical issues found

Critical issues

No critical issues found.

Other findings

None.

The fix correctly addresses the race window between the ctx.Done() select (line 153) and the newResult call (line 164) in execute(). When a session dies in that window, AfterFunc cancels executeCtx, causing newResult to return context.Canceled — previously non-retryable, now correctly wrapped with xerrors.Retryable. The approach is consistent with the existing ctx.Done() select block above it, and ctx.Err() is guaranteed non-nil at that point since executeCtx cancellation is triggered exclusively by AfterFunc(ctx, executeCancel). The CloseOnContextCancel mechanism in streamResult.nextPart further ensures that context.Canceled propagates even when cancellation occurs mid-Recv().


This review was generated automatically. Critical issues require attention; other findings are advisory.
If this comment was useful, please give it a 👍 — it helps us improve the review bot.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…race edge case

Adds a test where Recv() returns context.Canceled directly while the
parent ctx is still alive (user did NOT cancel). This is exactly the
production race: session death cancels the merged session ctx which
fires executeCancel, but the user context is untouched. Previously the
code wrapped ctx.Err() (nil) which would produce an invalid retryable
error; the current fix uses executeCtx.Err() with a fallback to err,
which this test verifies correctly produces a retryable context.Canceled.

Agent-Logs-Url: https://github.com/ydb-platform/ydb-go-sdk/sessions/bcf414c2-20d4-4c82-93c0-86a7ab4d0605

Co-authored-by: asmyasnikov <14202262+asmyasnikov@users.noreply.github.com>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@robot-vibe-db

robot-vibe-db Bot commented May 3, 2026

Copy link
Copy Markdown

AI Review Summary

Verdict: ✅ No critical issues found

Critical issues

No critical issues found.

Other findings

  • Minor | Low: The xerrors.Is(err, context.Canceled) check at internal/query/execute_query.go:175 catches all context.Canceled errors from newResult, not only those caused by the specific session-death race. This is intentionally safe — the retry loop operates on the caller's context and will stop naturally if the user truly cancelled — but a reader unfamiliar with the retry contract might initially wonder whether unrelated context cancellations are being misclassified. The existing code comment explains the semantics well.
  • Nit | Low: The SessionDiesBeforeFirstRecv_ParentCtxAlive test at internal/query/execute_query_test.go:558 exercises the cancelErr == nil fallback path (because the mock returns context.Canceled without executeCtx actually being cancelled). In production the primary path is executeCtx.Err() != nil. The test comments document this limitation, but a follow-up test that actually cancels executeCtx via a mock session-death signal would give higher confidence that the production path is covered end-to-end.

This review was generated automatically. Critical issues require attention; other findings are advisory.
If this comment was useful, please give it a 👍 — it helps us improve the review bot.

Comment thread internal/query/execute_query.go
Comment thread internal/query/execute_query_test.go Outdated
@asmyasnikov

Copy link
Copy Markdown
Member

@copilot your new code in PR produce flaky test yet

--- FAIL: TestBasicExampleQuery (0.20s)
    --- FAIL: TestBasicExampleQuery/ExecuteDataQuery (0.00s)
        basic_example_query_test.go:392: 
            	Error Trace:	/home/runner/work/ydb-go-sdk/ydb-go-sdk/tests/integration/basic_example_query_test.go:392
            	Error:      	Received unexpected error:
            	            	pool.With failed with 1 attempts: non-retryable error occurred on attempt No.1 (idempotent=false): 'context canceled' at `runtime.goexit(asm_amd64.s:1700)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/balancer.(*Balancer).nextConn(balancer.go:476)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/balancer.(*Balancer).wrapCall(balancer.go:422)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.execute(execute_query.go:140)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).execute(session.go:182)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).queryRow(session.go:70)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.clientQueryRow.func1(client.go:330)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.do.func1(client.go:224)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).try(pool.go:505)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With.func2(pool.go:533)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.Retry.func1(retry.go:264)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.opWithRecover(retry.go:418)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.RetryWithResult(retry.go:358)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.Retry(retry.go:270)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With(pool.go:539)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.do(client.go:232)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.clientQueryRow(client.go:336)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).QueryRow(client.go:362)`
            	Test:       	TestBasicExampleQuery/ExecuteDataQuery

The CI failure showed a second race window: when the session-merged ctx
(user ctx + session lifetime via xcontext.WithDone) is already cancelled
before execute() calls c.ExecuteQuery, AfterFunc fires executeCancel
immediately, and the balancer's nextConn returns context.Canceled from
executeCtx.Err() check at balancer.go:475.

The error propagated to execute_query.go:140 as non-retryable, causing
pool.With to fail instead of retrying with a fresh session.

Fix: apply the same ctx.Done() select check in the c.ExecuteQuery error
path (mirroring the existing check at lines 153-160), so that if ctx is
done when ExecuteQuery fails, we return xerrors.Retryable(ctx.Err()).

Also add SessionDiesBeforeExecuteQuery test to cover the CI failure.

Agent-Logs-Url: https://github.com/ydb-platform/ydb-go-sdk/sessions/44276cc9-3763-4bf2-951d-9e136a57e4ed

Co-authored-by: asmyasnikov <14202262+asmyasnikov@users.noreply.github.com>

Copilot AI commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot your new code in PR produce flaky test yet

--- FAIL: TestBasicExampleQuery (0.20s)

...

Fixed in commit 71d6d33.

The CI failure was a second race window: Session.execute merges user ctx with session lifetime via xcontext.WithDone, so when the session dies the merged ctx is already cancelled before execute() calls c.ExecuteQuery. AfterFunc(ctx, executeCancel) fires immediately, executeCtx is cancelled, and the balancer's nextConn returns context.Canceled at line 475. This error hit execute_query.go:140 as non-retryable.

The fix applies the same select { case <-ctx.Done(): return retryable ... } check in the c.ExecuteQuery error path (mirroring the existing check that follows it), plus a new SessionDiesBeforeExecuteQuery test that directly reproduces the CI failure scenario.

Copilot AI changed the title fix: context.Canceled from newResult propagated as non-retryable causing flaky TestBasicExampleQuery/ExecuteDataQuery fix: context.Canceled propagated as non-retryable in execute() causing flaky TestBasicExampleQuery/ExecuteDataQuery May 3, 2026
Copilot AI requested a review from asmyasnikov May 3, 2026 18:57
@asmyasnikov asmyasnikov added the SLO label May 3, 2026
@github-actions github-actions Bot removed the SLO label May 3, 2026
Comment thread internal/query/execute_query_test.go Outdated
@asmyasnikov asmyasnikov added the SLO label May 4, 2026
@github-actions github-actions Bot removed the SLO label May 4, 2026
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

🌋 SLO Test Results

🔴 8 workload(s) tested — 1 workload(s) exceeded failure thresholds

Commit: 8f5bdc7 · View run

Workload Thresholds Duration Report
native-query 🟢 OK 10m 1s 📄 Report
native-table-over-query-service 🔴 Failure 10m 1s 📄 Report
database-sql-query 🟢 OK 10m 1s 📄 Report
native-bulk-upsert 🟡 Warning 10m 📄 Report
native-table 🟡 Warning 10m 1s 📄 Report
database-sql-table 🟡 Warning 10m 1s 📄 Report
native-table-node-hints 🟢 OK 10m 11s 📄 Report
native-query-node-hints 🟢 OK 0s 📄 Report

Threshold violations:

native-table-over-query-service:

  • read_retry_attempts: Regression 200.0% >= critical 50%

native-bulk-upsert:

  • read_retry_attempts: Value 0.01 > warning max 0

native-table:

  • write_retry_attempts: Regression 27.3% >= warning 20%

database-sql-table:

  • read_retry_attempts: Regression 32.1% >= warning 20%

Generated by ydb-slo-action

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dev: flaky TestBasicExampleQuery/ExecuteDataQuery

4 participants