Skip to content

Improve error handling on AWS DB failover - #39841

Merged
getvictor merged 4 commits into
mainfrom
victor/39228-db-failover
Feb 17, 2026
Merged

Improve error handling on AWS DB failover#39841
getvictor merged 4 commits into
mainfrom
victor/39228-db-failover

Conversation

@getvictor

@getvictor getvictor commented Feb 13, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #39228

Manually tested by triggering a failover on loadtest.

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Bug Fixes
    • Health checks now detect a primary DB becoming read-only and report failure so the service restarts and reconnects to a writable primary.
    • Write failures due to DB read-only state now trigger immediate fatal handling to prompt graceful shutdown and recovery.
    • Improved detection and handling of read-only DB conditions to increase stability during failovers.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@getvictor
getvictor requested a review from Copilot February 13, 2026 16:24
@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Health-check and error handling for MySQL were extended to detect and react to writer demotion/read-only state. HealthCheck now queries SELECT @@read_only. A new exported IsReadOnlyError(error) bool detects MySQL read-only error codes (1792, 1290, 1836). A one-shot fatal-error handler (SetFatalErrorHandler / triggerFatalError) was added and invoked from retry/transaction paths when read-only errors occur. The serve path wires a channel to trigger graceful shutdown on fatal DB errors. Tests exercising the new detection and handler were added.

Possibly related PRs

  • Refactor common_mysql #37245 — Modifies the same platform/mysql files (errors.go, retry.go, common.go) to add read-only error detection and a one-shot fatal-error handler; strongly overlaps with this change.
🚥 Pre-merge checks | ✅ 3 | ❌ 3
❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (33 files):

⚔️ android/CHANGELOG.md (content)
⚔️ android/app/build.gradle.kts (content)
⚔️ articles/enroll-byod-ios-ipados-hosts.md (content)
⚔️ articles/fleet-troubleshooting-for-it-admins.md (content)
⚔️ cmd/fleet/serve.go (content)
⚔️ ee/maintained-apps/outputs/7-zip/windows.json (content)
⚔️ ee/maintained-apps/outputs/anydesk/darwin.json (content)
⚔️ ee/maintained-apps/outputs/arc/darwin.json (content)
⚔️ ee/maintained-apps/outputs/brave-browser/darwin.json (content)
⚔️ ee/maintained-apps/outputs/canva/darwin.json (content)
⚔️ ee/maintained-apps/outputs/claude/darwin.json (content)
⚔️ ee/maintained-apps/outputs/cursor/darwin.json (content)
⚔️ ee/maintained-apps/outputs/dialpad/darwin.json (content)
⚔️ ee/maintained-apps/outputs/google-chrome/darwin.json (content)
⚔️ ee/maintained-apps/outputs/google-chrome/windows.json (content)
⚔️ ee/maintained-apps/outputs/grammarly-desktop/darwin.json (content)
⚔️ ee/maintained-apps/outputs/jabra-direct/darwin.json (content)
⚔️ ee/maintained-apps/outputs/lens/darwin.json (content)
⚔️ ee/maintained-apps/outputs/teamviewer/windows.json (content)
⚔️ ee/maintained-apps/outputs/transmit/darwin.json (content)
⚔️ ee/maintained-apps/outputs/visual-studio-code/darwin.json (content)
⚔️ ee/maintained-apps/outputs/windsurf/darwin.json (content)
⚔️ ee/maintained-apps/outputs/zoom/darwin.json (content)
⚔️ frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx (content)
⚔️ server/activity/internal/service/endpoint_utils.go (content)
⚔️ server/activity/internal/service/handler_test.go (content)
⚔️ server/activity/internal/service/service.go (content)
⚔️ server/datastore/mysql/mysql.go (content)
⚔️ server/platform/mysql/common.go (content)
⚔️ server/platform/mysql/errors.go (content)
⚔️ server/platform/mysql/retry.go (content)
⚔️ server/service/testing_client.go (content)
⚔️ yarn.lock (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
Description check ⚠️ Warning PR description lacks detail on the implementation approach and testing beyond the checklist. While a changes file is confirmed and automated tests are added, the description does not explain what was implemented or how it resolves the issue. Provide a clear summary of the implementation approach (e.g., how health checks detect read-only mode, how fatal errors trigger graceful shutdown) and details on manual testing performed during failover scenarios.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the main change: improving error handling specifically on AWS DB failover scenarios.
Linked Issues check ✅ Passed The implementation comprehensively addresses all objectives from #39228: HealthCheck detects read-only via @@read_only, read-only errors are identified and trigger fatal error handlers, and graceful shutdown is initiated on DB failover.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing DB failover handling from #39228; no unrelated modifications are present in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch victor/39228-db-failover
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch victor/39228-db-failover
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/platform/mysql/retry.go (1)

42-53: ⚠️ Potential issue | 🟡 Minor

Read-only error check is skipped when rollback also fails.

If fn(tx) returns a read-only error and the subsequent tx.Rollback() also fails (plausible during a failover — connections may be broken), the code at line 46 wraps the original error as backoff.Permanent and returns without ever reaching the IsReadOnlyError check. This means the panic-to-restart path is bypassed.

The health check (SELECT @@read_only) provides a safety net, so this isn't critical, but you could move the read-only check before the rollback-error branch to ensure consistent behavior:

Proposed fix
 		if err := fn(tx); err != nil {
 			rbErr := tx.Rollback()
+
+			// Read-only errors indicate a DB failover occurred (primary demoted to reader).
+			// Panic to force Fleet to restart and reconnect to the new primary.
+			if IsReadOnlyError(err) {
+				panic(fmt.Sprintf("database is read-only, possible failover detected: %v", err))
+			}
+
 			if rbErr != nil && rbErr != sql.ErrTxDone {
 				// Consider rollback errors to be non-retryable
 				return backoff.Permanent(ctxerr.Wrapf(ctx, err, "got err '%s' rolling back after err", rbErr.Error()))
 			}
 
-			// Read-only errors indicate a DB failover occurred (primary demoted to reader).
-			// Panic to force Fleet to restart and reconnect to the new primary.
-			if IsReadOnlyError(err) {
-				panic(fmt.Sprintf("database is read-only, possible failover detected: %v", err))
-			}
-
 			if retryableError(err) {
🧹 Nitpick comments (1)
server/platform/mysql/errors_test.go (1)

14-44: Good test coverage for the core error codes.

Consider adding a case for a non-MySQL error (e.g., errors.New("connection reset")) to explicitly verify that non-*mysql.MySQLError types return false. Currently only a *mysql.MySQLError with a non-matching number is tested.

Suggested additional test case
 		{
+			name: "non-MySQL error",
+			err:  errors.New("connection reset by peer"),
+			want: false,
+		},
+		{
 			name: "unrelated MySQL error",

(Would require adding "errors" to imports.)

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

This PR aims to improve Fleet’s behavior during AWS Aurora MySQL failovers by detecting when the “primary” connection is actually read-only (demoted writer) and surfacing that condition more aggressively so orchestration can restart Fleet and re-establish correct DB connections.

Changes:

  • Add MySQL read-only error detection (IsReadOnlyError) based on specific MySQL error codes.
  • Update transaction retry helper to panic on detected read-only DB errors.
  • Update MySQL datastore health check to fail when @@read_only=1 on the primary connection.

Reviewed changes

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

Show a summary per file
File Description
server/platform/mysql/retry.go Panics on read-only MySQL errors during transaction execution/commit.
server/platform/mysql/errors.go Adds IsReadOnlyError helper and read-only-related MySQL error constants.
server/platform/mysql/errors_test.go Unit tests for IsReadOnlyError.
server/datastore/mysql/mysql.go Changes health check to query SELECT @@read_only and fail if primary is read-only.
changes/39228-db-failover Adds release note entry describing the failover handling behavior change.

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

Comment thread server/platform/mysql/retry.go
Comment thread server/platform/mysql/retry.go
Comment thread server/platform/mysql/retry.go
@codecov

codecov Bot commented Feb 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.60%. Comparing base (44aebdf) to head (3b2262c).
⚠️ Report is 53 commits behind head on main.

Files with missing lines Patch % Lines
cmd/fleet/serve.go 0.00% 9 Missing ⚠️
server/datastore/mysql/sessions.go 0.00% 2 Missing ⚠️
server/datastore/mysql/mysql.go 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #39841      +/-   ##
==========================================
- Coverage   66.26%   64.60%   -1.66%     
==========================================
  Files        2439     2438       -1     
  Lines      195405   195463      +58     
  Branches     8539     8539              
==========================================
- Hits       129487   126282    -3205     
- Misses      54189    57578    +3389     
+ Partials    11729    11603     -126     
Flag Coverage Δ
backend 66.14% <75.00%> (-1.92%) ⬇️

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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@getvictor
getvictor requested a review from Copilot February 13, 2026 20:51
@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/platform/mysql/retry.go`:
- Around line 40-52: The race arises because triggerFatalError calls
fatalErrorOnce.Do(...) without holding fatalErrorMu, while SetFatalErrorHandler
may reset fatalErrorOnce under the write lock; fix by acquiring the read lock
(fatalErrorMu.RLock()) before reading fatalErrorHandler and keep that read lock
held across the fatalErrorOnce.Do(...) call so the Once state cannot be
concurrently reset; specifically, in triggerFatalError, RLock, read
fatalErrorHandler into a local, if handler==nil then RUnlock and panic as
before, otherwise call fatalErrorOnce.Do(func(){ handler(err) }) while still
holding the RLock, then RUnlock afterward; this uses the existing symbols
fatalErrorMu, fatalErrorHandler, fatalErrorOnce, triggerFatalError and avoids
deadlocks because the handler should not call SetFatalErrorHandler.

Comment thread server/platform/mysql/retry.go Outdated

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 8 out of 8 changed files in this pull request and generated 3 comments.


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

Comment thread server/platform/mysql/retry_test.go
Comment thread server/datastore/mysql/mysql.go
Comment thread changes/39228-db-failover Outdated
@getvictor
getvictor marked this pull request as ready for review February 14, 2026 02:01
@getvictor
getvictor requested a review from a team as a code owner February 14, 2026 02:01

@ksykulev ksykulev 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.

👍

@getvictor
getvictor merged commit 42d5f1f into main Feb 17, 2026
42 of 45 checks passed
@getvictor
getvictor deleted the victor/39228-db-failover branch February 17, 2026 13:10
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.

Fleet write failures after DB Failover

3 participants