Skip to content

feat: upgrade to Go 1.26 - #68

Merged
bxcodec merged 2 commits into
mainfrom
feat/upgrade-to-go126
Jul 18, 2026
Merged

feat: upgrade to Go 1.26#68
bxcodec merged 2 commits into
mainfrom
feat/upgrade-to-go126

Conversation

@bxcodec

@bxcodec bxcodec commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Bumps the module to Go 1.26 and brings the build tooling with it. The code changes are mechanical; the CI side needed real work to stay green.

Code

  • go.mod now requires go 1.26.0
  • Replaced interface{} with any across the DB, Conn, Stmt, and Tx wrapper interfaces
  • Switched integer count loops to for i := range n

Tooling (the part that actually broke)

golangci-lint 1.59.0 is built with Go 1.22 and can't read Go 1.26 export data. It failed with unsupported version: 2 and threw fake typecheck errors on every file, which would have blocked the lint gate in go.yml. Fixing it meant moving to golangci-lint v2:

  • Bumped golangci-lint 1.59.0 to 2.12.2 (built with go1.26.2) in misc/makefile/tools.Makefile
  • Migrated .golangci.yaml to the v2 config format. gofmt and goimports moved into a formatters block, and the linters that no longer exist in v2 dropped out: exportloopref (pointless under Go 1.22 loopvar), plus gosimple and stylecheck (both folded into staticcheck)
  • Removed depguard. It was enabled with no rules, so v2 defaulted to denying the module's own go.uber.org/multierr import. It only stayed quiet before because typecheck aborted first. There was no guard intent, so it's gone rather than faked with a deny list
  • Added a G115 exclusion for the load balancer counter modulo. x % uint64(n) is bounded by n and always fits, so the overflow warning is a false positive. Matches the existing G404 exclusion style
  • Set the lint target to go: "1.26"

Workflows

  • go_cross.yml: matrix moved from 1.21.x/1.22.x to 1.26.x plus stable. The old versions can't build a module that requires 1.26
  • go_fuzz.yml: dropped the hardcoded go-version: 1.20.4 in favor of go-version-file: go.mod
  • Bumped stale actions: checkout@v3 to v4, setup-go@v4 to v5, cache@v3 to v4

Verification

Run locally against Go 1.26.5:

Check Result
go build ./... passes
go vet ./... clean
make lint (golangci-lint 2.12.2) 0 issues
make test 27 tests pass
go test -fuzz=FuzzMultiWrite full corpus passes, no crashes

Fuzz harness fix

Once CI could actually run the linter and tests, FuzzMultiWrite turned out to be red, and it had been red on main for a while too. Not a Go 1.26 regression, but it was blocking this branch, so it's fixed here.

The testMW helper guessed which mock the next query would hit by calling loadBalancer.predict(), then armed that mock. But predict() advances the round-robin counter, and the resolver advances it again on the real query, so any cluster with more than one DB routed to a different mock than the test armed. Every round-robin config with >1 primary or replica failed with sqlmock unmet-expectation errors. Random happened to work because its predict() preloads a channel that Resolve() then consumes.

The fix keeps production code untouched and replaces the guess with a test-only nextIndex() helper that computes the upcoming round-robin index without mutating the counter (and keeps the channel-preload path for random), so the prediction matches the resolver's routing for any cluster size.

One housekeeping note: CI had auto-committed a crashing fuzz corpus back onto this branch (ci: fuzz tests updated on). That commit is dropped, since the harness fix makes those inputs pass and fuzzing no longer produces crashes.

bxcodec added 2 commits July 18, 2026 15:45
testMW called loadBalancer.predict() to guess which mock the next query
would hit, then armed that mock. But predict() advances the round-robin
counter, and the resolver's own Resolve() advances it again, so for pools
with more than one db the query landed on a different mock than the one
armed. Every fuzz config with >1 primary or replica under the round-robin
policy failed with sqlmock unmet-expectation errors; random happened to
work because its predict() preloads a channel that Resolve() consumes.

Replace the guess with a test-only nextIndex() helper that computes the
upcoming round-robin index without mutating the counter (and keeps the
channel-preload path for random), so the prediction matches the resolver's
routing for any cluster size.
@bxcodec
bxcodec force-pushed the feat/upgrade-to-go126 branch from 3e1d7b7 to dab9aea Compare July 18, 2026 13:08
@bxcodec
bxcodec merged commit 6b8a996 into main Jul 18, 2026
17 checks passed
@bxcodec
bxcodec deleted the feat/upgrade-to-go126 branch July 18, 2026 13:13
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.

1 participant