From fdfccc3a2d55af658df00a3fe785b6117923d4e4 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Wed, 11 Feb 2026 10:01:48 -0600 Subject: [PATCH 1/4] use Go 1.25 + 1.26 Keeping with our policy of supporting the latest major Go release as well as the prior one, this upgrades the project to a minimum of Go 1.25 now that 1.26 has been released. --- .github/workflows/ci.yaml | 12 ++++++------ CHANGELOG.md | 4 ++++ cmd/river/go.mod | 4 ++-- go.mod | 4 ++-- go.work | 4 ++-- riverdriver/go.mod | 4 ++-- riverdriver/riverdatabasesql/go.mod | 4 ++-- riverdriver/riverdrivertest/go.mod | 4 ++-- riverdriver/riverpgxv5/go.mod | 4 ++-- riverdriver/riversqlite/go.mod | 4 ++-- rivershared/go.mod | 4 ++-- rivertype/go.mod | 4 ++-- 12 files changed, 30 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 854a201e..5ad39f56 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,21 +29,21 @@ jobs: matrix: include: # Run the latest Go version against all supported Postgres versions: - - go-version: "1.25" + - go-version: "1.26" postgres-version: 18 - - go-version: "1.25" + - go-version: "1.26" postgres-version: 17 - - go-version: "1.25" + - go-version: "1.26" postgres-version: 16 - - go-version: "1.25" + - go-version: "1.26" postgres-version: 15 - - go-version: "1.25" + - go-version: "1.26" postgres-version: 14 # Also run the previous Go version (the Go version previous to current # is the only other officially supported Go version) against the # latest Postgres version: - - go-version: "1.24" + - go-version: "1.25" postgres-version: 18 fail-fast: false timeout-minutes: 5 diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2864d0..d2b35288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added root River CLI flag `--statement-timeout` so Postgres session statement timeout can be set explicitly for commands like migrations. Explicit flag values take priority over database URL query params, and query params still take priority over built-in defaults. [PR #1142](https://github.com/riverqueue/river/pull/1142). +### Changed + +- Upgrade supported Go versions to 1.25 and 1.26, and update CI accordingly. [PR #1144](https://github.com/riverqueue/river/pull/1144). + ### Fixed - `JobCountByQueueAndState` now returns consistent results across drivers, including requested queues with zero jobs, and deduplicates repeated queue names in input. This resolves an issue with the sqlite driver in River UI reported in [riverqueue/riverui#496](https://github.com/riverqueue/riverui#496). [PR #1140](https://github.com/riverqueue/river/pull/1140). diff --git a/cmd/river/go.mod b/cmd/river/go.mod index 75c0a5d6..ca5b20f5 100644 --- a/cmd/river/go.mod +++ b/cmd/river/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/cmd/river -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/jackc/pgx/v5 v5.8.0 diff --git a/go.mod b/go.mod index 31ee175f..3e5444e0 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 diff --git a/go.work b/go.work index c2482089..9a9927fe 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,6 @@ -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 use ( . diff --git a/riverdriver/go.mod b/riverdriver/go.mod index e5fdf222..ff4143a6 100644 --- a/riverdriver/go.mod +++ b/riverdriver/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/riverdriver -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/riverqueue/river/rivertype v0.30.2 diff --git a/riverdriver/riverdatabasesql/go.mod b/riverdriver/riverdatabasesql/go.mod index 2d9c2d5b..3fb682ab 100644 --- a/riverdriver/riverdatabasesql/go.mod +++ b/riverdriver/riverdatabasesql/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/riverdriver/riverdatabasesql -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/jackc/pgx/v5 v5.8.0 diff --git a/riverdriver/riverdrivertest/go.mod b/riverdriver/riverdrivertest/go.mod index 0d2ee6da..d71fceb2 100644 --- a/riverdriver/riverdrivertest/go.mod +++ b/riverdriver/riverdrivertest/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/riverdriver/riverdrivertest -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/davecgh/go-spew v1.1.1 diff --git a/riverdriver/riverpgxv5/go.mod b/riverdriver/riverpgxv5/go.mod index 55223a4b..c9f80cae 100644 --- a/riverdriver/riverpgxv5/go.mod +++ b/riverdriver/riverpgxv5/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/riverdriver/riverpgxv5 -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/jackc/pgx/v5 v5.8.0 diff --git a/riverdriver/riversqlite/go.mod b/riverdriver/riversqlite/go.mod index 58cc91c5..634cf1bd 100644 --- a/riverdriver/riversqlite/go.mod +++ b/riverdriver/riversqlite/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/riverdriver/riversqlite -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/riverqueue/river v0.30.2 diff --git a/rivershared/go.mod b/rivershared/go.mod index 82455a66..84e0f7ab 100644 --- a/rivershared/go.mod +++ b/rivershared/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/rivershared -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require ( github.com/jackc/pgx/v5 v5.8.0 diff --git a/rivertype/go.mod b/rivertype/go.mod index b441e9ad..70ea92ca 100644 --- a/rivertype/go.mod +++ b/rivertype/go.mod @@ -1,8 +1,8 @@ module github.com/riverqueue/river/rivertype -go 1.24.0 +go 1.25.0 -toolchain go1.25.2 +toolchain go1.25.7 require github.com/stretchr/testify v1.11.1 From 7d38b73fb8ce91dea6a010627f358f7013b8b2f3 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Fri, 13 Feb 2026 09:31:46 -0600 Subject: [PATCH 2/4] use golangci-lint v2.9.0, actions v9 --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5ad39f56..37981f10 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -253,7 +253,7 @@ jobs: name: lint runs-on: ubuntu-latest env: - GOLANGCI_LINT_VERSION: v2.7.2 + GOLANGCI_LINT_VERSION: v2.9.0 permissions: contents: read # allow read access to pull request. Use with `only-new-issues` option. @@ -269,7 +269,7 @@ jobs: uses: actions/checkout@v4 - name: Lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@v9 with: # golangci-lint needs to be run separately for every Go module, and # its GitHub Action doesn't provide any way to do that. Have it fetch From 19d216157fdb13a7f7ea43242ae705142087b4d7 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Fri, 13 Feb 2026 09:35:13 -0600 Subject: [PATCH 3/4] golangci-lint auto fixes for strings.Cut, wg.Go --- client_test.go | 12 ++++------- internal/hooklookup/hook_lookup_test.go | 14 ++++--------- .../middleware_lookup_test.go | 7 ++----- internal/notifier/notifier.go | 6 ++---- retry_policy_test.go | 6 ++---- .../slogtest/slog_test_handler_test.go | 6 ++---- .../sqlctemplate/sqlc_template_test.go | 21 ++++++------------- rivershared/startstop/start_stop_test.go | 6 ++---- rivershared/startstoptest/startstoptest.go | 7 ++----- rivershared/structtag/struct_tag.go | 4 ++-- 10 files changed, 28 insertions(+), 61 deletions(-) diff --git a/client_test.go b/client_test.go index 90ccc43e..af80eb04 100644 --- a/client_test.go +++ b/client_test.go @@ -1523,9 +1523,7 @@ func Test_Client_Stop_Common(t *testing.T) { var wg sync.WaitGroup for range 10 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case <-ctx.Done(): @@ -1546,7 +1544,7 @@ func Test_Client_Stop_Common(t *testing.T) { // Sleep a brief time between inserts. serviceutil.CancellableSleep(ctx, randutil.DurationBetween(1*time.Microsecond, 10*time.Millisecond)) } - }() + }) } return func() { @@ -6552,11 +6550,9 @@ func Test_Client_SubscribeConfig(t *testing.T) { // Need to start waiting on events before running the client or the // channel could overflow before we start listening. var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { _ = riversharedtest.WaitOrTimeoutN(t, subscribeChan, numJobsToInsert) - }() + }) startClient(ctx, t, client) diff --git a/internal/hooklookup/hook_lookup_test.go b/internal/hooklookup/hook_lookup_test.go index 2b931135..4390ca6c 100644 --- a/internal/hooklookup/hook_lookup_test.go +++ b/internal/hooklookup/hook_lookup_test.go @@ -67,14 +67,11 @@ func TestHookLookup(t *testing.T) { var wg sync.WaitGroup parallelLookupLoop := func(kind HookKind) { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { for range 50 { hookLookup.ByHookKind(kind) } - }() + }) } parallelLookupLoop(HookKindInsertBegin) @@ -165,14 +162,11 @@ func TestJobHookLookup(t *testing.T) { var wg sync.WaitGroup parallelLookupLoop := func(args rivertype.JobArgs) { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { for range 50 { jobHookLookup.ByJobArgs(args) } - }() + }) } parallelLookupLoop(&jobArgsNoHooks{}) diff --git a/internal/middlewarelookup/middleware_lookup_test.go b/internal/middlewarelookup/middleware_lookup_test.go index 806f28c8..9b5f9a68 100644 --- a/internal/middlewarelookup/middleware_lookup_test.go +++ b/internal/middlewarelookup/middleware_lookup_test.go @@ -60,14 +60,11 @@ func TestMiddlewareLookup(t *testing.T) { var wg sync.WaitGroup parallelLookupLoop := func(kind MiddlewareKind) { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { for range 50 { middlewareLookup.ByMiddlewareKind(kind) } - }() + }) } parallelLookupLoop(MiddlewareKindJobInsert) diff --git a/internal/notifier/notifier.go b/internal/notifier/notifier.go index e36f47d0..42d3a63a 100644 --- a/internal/notifier/notifier.go +++ b/internal/notifier/notifier.go @@ -133,11 +133,9 @@ func (n *Notifier) Start(ctx context.Context) error { var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { n.deliverNotifications(ctx) - }() + }) for attempt := 0; ; attempt++ { if err := n.listenAndWait(ctx); err != nil { diff --git a/retry_policy_test.go b/retry_policy_test.go index 0aab002c..3797c6f1 100644 --- a/retry_policy_test.go +++ b/retry_policy_test.go @@ -190,13 +190,11 @@ func TestDefaultRetryPolicy_stress(t *testing.T) { // Hit the source with a bunch of goroutines to help suss out any problems // with concurrent safety (when combined with `-race`). for range 10 { - wg.Add(1) - go func() { + wg.Go(func() { for range 100 { _ = retryPolicy.retrySeconds(7) } - wg.Done() - }() + }) } wg.Wait() diff --git a/rivershared/slogtest/slog_test_handler_test.go b/rivershared/slogtest/slog_test_handler_test.go index 28aac090..7adec12f 100644 --- a/rivershared/slogtest/slog_test_handler_test.go +++ b/rivershared/slogtest/slog_test_handler_test.go @@ -48,13 +48,11 @@ func TestSlogTestHandler_stress(t *testing.T) { ) for range 10 { - wg.Add(1) - go func() { + wg.Go(func() { for range 100 { logger.InfoContext(ctx, "message", "key", "value") } - wg.Done() - }() + }) } wg.Wait() diff --git a/rivershared/sqlctemplate/sqlc_template_test.go b/rivershared/sqlctemplate/sqlc_template_test.go index 6689fa6a..119ebd54 100644 --- a/rivershared/sqlctemplate/sqlc_template_test.go +++ b/rivershared/sqlctemplate/sqlc_template_test.go @@ -440,10 +440,7 @@ func TestReplacer(t *testing.T) { var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { for i := range numIterations { ctx := WithReplacements(ctx, map[string]Replacement{ "schema": {Value: "test_schema."}, @@ -459,12 +456,9 @@ func TestReplacer(t *testing.T) { periodicallyClearCache(i, replacer) } - }() - - wg.Add(1) - go func() { - defer wg.Done() + }) + wg.Go(func() { for i := range numIterations { ctx := WithReplacements(ctx, map[string]Replacement{ "schema": {Stable: true, Value: "test_schema."}, @@ -480,12 +474,9 @@ func TestReplacer(t *testing.T) { periodicallyClearCache(i, replacer) } - }() - - wg.Add(1) - go func() { - defer wg.Done() + }) + wg.Go(func() { for i := range numIterations { ctx := WithReplacements(ctx, map[string]Replacement{ "schema": {Stable: true, Value: "test_schema."}, @@ -501,7 +492,7 @@ func TestReplacer(t *testing.T) { periodicallyClearCache(i, replacer) } - }() + }) wg.Wait() }) diff --git a/rivershared/startstop/start_stop_test.go b/rivershared/startstop/start_stop_test.go index 8b26f3d7..3b42dccc 100644 --- a/rivershared/startstop/start_stop_test.go +++ b/rivershared/startstop/start_stop_test.go @@ -123,14 +123,12 @@ func testService(t *testing.T, newService func(t *testing.T) serviceWithStopped) var wg sync.WaitGroup for range 10 { - wg.Add(1) - go func() { + wg.Go(func() { for range 50 { require.NoError(t, service.Start(ctx)) service.Stop() } - wg.Done() - }() + }) } wg.Wait() diff --git a/rivershared/startstoptest/startstoptest.go b/rivershared/startstoptest/startstoptest.go index ae08bcbf..3b3c3dc0 100644 --- a/rivershared/startstoptest/startstoptest.go +++ b/rivershared/startstoptest/startstoptest.go @@ -48,10 +48,7 @@ func StressErr(ctx context.Context, tb testingT, svc startstop.Service, allowedS } for range 10 { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { for range 50 { err := svc.Start(ctx) if err != nil && !isAllowedStartError(err) { @@ -71,7 +68,7 @@ func StressErr(ctx context.Context, tb testingT, svc startstop.Service, allowedS require.FailNow(tb, "Timed out waiting for service to stop") } } - }() + }) } wg.Wait() diff --git a/rivershared/structtag/struct_tag.go b/rivershared/structtag/struct_tag.go index 69b88211..a27b787e 100644 --- a/rivershared/structtag/struct_tag.go +++ b/rivershared/structtag/struct_tag.go @@ -177,8 +177,8 @@ func sortedFieldsWithTagUncached(typ reflect.Type, tagValue string, path []strin // It handles tags with options, e.g., `json:"recipient,omitempty"`. func parseJSONTag(tag string) string { // Tags can be like "recipient,omitempty", so split by comma - if commaIdx := strings.Index(tag, ","); commaIdx != -1 { - return tag[:commaIdx] + if before, _, ok := strings.Cut(tag, ","); ok { + return before } return tag } From bbc1379adceb13c6e68b1d9e41346093f8e45997 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Fri, 13 Feb 2026 09:36:08 -0600 Subject: [PATCH 4/4] lint fix: preallocate truncateTables before loop append --- riverdbtest/riverdbtest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riverdbtest/riverdbtest.go b/riverdbtest/riverdbtest.go index 72f026a1..e259b96f 100644 --- a/riverdbtest/riverdbtest.go +++ b/riverdbtest/riverdbtest.go @@ -259,7 +259,7 @@ func TestSchema[TTx any](ctx context.Context, tb testutil.TestingTB, driver rive // All tables to truncate when reusing a schema for this set of lines. Also // used to perform the post-flight cleanup check to make sure tests didn't // leave any detritus in the default schema. - var truncateTables []string + truncateTables := make([]string, 0, len(lines)) for _, line := range lines { var targetVersion int if opts.LineTargetVersions != nil {