Skip to content

🐛 bug: fix data race on lazy appListKeys generation in Render#4440

Merged
ReneWerner87 merged 1 commit into
mainfrom
fix-render-applistkeys-data-race
Jun 19, 2026
Merged

🐛 bug: fix data race on lazy appListKeys generation in Render#4440
ReneWerner87 merged 1 commit into
mainfrom
fix-render-applistkeys-data-race

Conversation

@ReneWerner87

Copy link
Copy Markdown
Member

Summary

Test_Ctx_RenderWithLocals intermittently fails the race detector in CI (see failed run). The test runs two parallel subtests (EmptyBind, NilBind) that share a single *App and call Render concurrently.

renderExtensions (ctx.go) lazily generated app.mountFields.appListKeys behind a non-atomic guard:

if len(c.app.mountFields.appListKeys) == 0 {
    c.app.generateAppListKeys()
}

The len check and the append inside generateAppListKeys (mount.go) are not atomic, so two concurrent Render calls both observe len == 0 and both write to the shared slice while the other reads it (the len read, and the slices.Backward iteration over the slice during Render). The race detector reports:

  • Write at mount.go:130 in generateAppListKeys (the append)
  • Read at ctx.go:834 / during Render in res.go

Fix

Introduce a dedicated sync.Once (appListKeysOnce) in mountFields and route both callers of generateAppListKeys through it:

  • the lazy path in renderExtensions (ctx.go)
  • the startup path in mountStartupProcess (mount.go), kept inside the existing subAppsProcessed.Do block, after appendSubAppLists

The keys are now generated exactly once with proper happens-before ordering; concurrent renders block until generation finishes and then read a slice that is never mutated again. Sharing a single Once between both callers also removes a latent double-append that the old code allowed if the lazy path ever ran before mountStartupProcess.

This preserves existing behavior: appListKeys was already generated only once in practice (the old len == 0 guard never re-fired, the slice is never reset, and appList does not grow after startup). Route rebuilds via RebuildTree() are unaffected, since they only rebuild the route prefix trees and never touch appListKeys.

Verification

  • go test -race -run 'Test_Ctx_RenderWithLocals$' -count=200 . passes clean. The original code trips the detector on the first iteration.
  • The full root package and all middleware packages pass with -race.
  • go vet ./... is clean.

🤖 Generated with Claude Code

Test_Ctx_RenderWithLocals runs two parallel subtests that share one app
and call Render concurrently. renderExtensions lazily generated
app.mountFields.appListKeys behind a non-atomic `len(...) == 0` guard, so
both goroutines could enter generateAppListKeys at once: one appending to
the slice while the other read its length and later iterated it during
Render, tripping the race detector.

Guard the generation with a dedicated sync.Once (appListKeysOnce) shared
by both callers (the lazy Render path and mountStartupProcess). The keys
are now generated exactly once with proper happens-before ordering;
concurrent renders block until the first generation completes. Sharing one
Once also removes a latent double-append that was possible when the lazy
path ran before mountStartupProcess.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ReneWerner87 ReneWerner87 requested a review from a team as a code owner June 19, 2026 07:15
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 54a7ebcb-f90a-450c-abc9-95c9ccc1a937

📥 Commits

Reviewing files that changed from the base of the PR and between 099802b and 8dee0cd.

📒 Files selected for processing (2)
  • ctx.go
  • mount.go

Walkthrough

A sync.Once field (appListKeysOnce) is added to mountFields in mount.go. Both mountStartupProcess and renderExtensions are updated to call generateAppListKeys through this guard, replacing a direct call and a length-based conditional check respectively.

Changes

sync.Once guard for appListKeys

Layer / File(s) Summary
sync.Once guard in mountFields and call sites
mount.go, ctx.go
mountFields gains appListKeysOnce sync.Once; mountStartupProcess replaces the direct generateAppListKeys() call with appListKeysOnce.Do(...), and renderExtensions replaces the len == 0 conditional check with the same Do pattern.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Suggested labels

☢️ Bug, v3, codex

Suggested reviewers

  • efectn
  • sixcolors

Poem

🐇 Once upon a sync, a rabbit declared,
"No keys regenerated — none shall be spared!
A Once is enough, the guard stands tall,
One Do to rule them, one call for all.
Hop hop hooray, the race is now fair!" 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides a comprehensive explanation of the race condition, the root cause, the fix using sync.Once, and verification results; however, it lacks completion of the template checklist items and missing sections like 'Changes introduced' and 'Type of change'. Complete the PR template by marking relevant checklist items (e.g., 'Code consistency', 'Performance improvement') and adding a 'Type of change' section to clarify the classification of this fix.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: fixing a data race on lazy appListKeys generation in Render, matching the code modifications to ctx.go and mount.go.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-render-applistkeys-data-race

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

@ReneWerner87 ReneWerner87 added this to v3 Jun 19, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jun 19, 2026
@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.59%. Comparing base (099802b) to head (8dee0cd).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4440      +/-   ##
==========================================
- Coverage   91.59%   91.59%   -0.01%     
==========================================
  Files         134      134              
  Lines       13518    13517       -1     
==========================================
- Hits        12382    12381       -1     
  Misses        722      722              
  Partials      414      414              
Flag Coverage Δ
unittests 91.59% <100.00%> (-0.01%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 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.

@ReneWerner87 ReneWerner87 merged commit 5de21e9 into main Jun 19, 2026
22 checks passed
@ReneWerner87 ReneWerner87 deleted the fix-render-applistkeys-data-race branch June 19, 2026 07:55
@github-project-automation github-project-automation Bot moved this to Done in v3 Jun 19, 2026
@ReneWerner87 ReneWerner87 modified the milestones: v3, v3.4.0 Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant