Skip to content

🐛 bug: synchronize view reloads with template rendering#4288

Open
gaby wants to merge 6 commits into
mainfrom
fix-reloadviews-concurrency-issue
Open

🐛 bug: synchronize view reloads with template rendering#4288
gaby wants to merge 6 commits into
mainfrom
fix-reloadviews-concurrency-issue

Conversation

@gaby
Copy link
Copy Markdown
Member

@gaby gaby commented May 16, 2026

Motivation

  • ReloadViews exposed a runtime API that calls Views.Load() while requests may concurrently call Views.Render(), causing data races and crashes such as concurrent map read/write.
  • The intent is to prevent concurrent Load/Render races without changing view-engine APIs or behavior during startup.
  • Provide a minimal runtime synchronization that preserves existing functionality and mounted-app semantics.

Description

  • Added a dedicated viewsMutex sync.RWMutex to App to coordinate engine access across reload and render paths in app.go.
  • Updated ReloadViews() to acquire an exclusive viewsMutex lock for each target app before calling Views.Load() and release it after the call in app.go.
  • Updated DefaultRes.Render() to acquire a read lock (RLock) around Views.Render() so rendering and reloads are properly synchronized in res.go.
  • Added a regression test Test_App_ReloadViews_BlocksRenderUntilLoadCompletes in app_test.go that verifies a render blocks until an in-flight ReloadViews() finishes, and the test uses t.Parallel() as required.

Copilot AI review requested due to automatic review settings May 16, 2026 14:03
@gaby gaby requested a review from a team as a code owner May 16, 2026 14:03
@gaby gaby requested a review from sixcolors May 16, 2026 14:03
@gaby gaby requested review from ReneWerner87 and efectn May 16, 2026 14:03
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces per-Views synchronization to serialize view template operations. It adds a reflection-based lock store that maps each Views instance to a *sync.RWMutex, integrates write locking in ReloadViews() around Views.Load(), read locking in DefaultRes.Render() around Views.Render(), and provides tests for concurrent blocking and panic-recovery scenarios across mounted apps.

Changes

View Engine Thread Safety

Layer / File(s) Summary
Per-Views lock store and keying
app.go
Implements reflection-derived viewsLockKey, a global viewsLockStore map guarded by mutex, and getViewsLock(views) helper to lazily create and cache *sync.RWMutex per distinct Views instance.
ReloadViews write-lock and Render read-lock
app.go, res.go
ReloadViews() acquires per-Views write lock before calling Views.Load() to serialize reload operations; DefaultRes.Render() acquires per-Views read lock before calling Views.Render() to allow concurrent renders while blocking during reload. Error wrapping preserved in both paths.
Test infrastructure and concurrency/panic scenarios
app_test.go
Adds blocking and panic-inducing view test doubles (blockingView, sharedBlockingView, panicLoadView, panicRenderView) plus five test functions verifying: ReloadViews blocks concurrent Render, panics during Load/Render are recovered and unlock waiting operations, and shared view engines synchronize across sibling and reused mounted apps.

Sequence Diagram(s)

sequenceDiagram
  participant Reload as ReloadViews()
  participant Render as DefaultRes.Render()
  participant Lock as *sync.RWMutex
  participant Views as Views.Load/Render
  Reload->>Lock: Lock() (write)
  Render->>Lock: RLock() (blocked)
  Reload->>Views: Load()
  Views-->>Reload: complete
  Reload->>Lock: Unlock()
  Lock-->>Render: RLock acquired
  Render->>Views: Render()
  Views-->>Render: complete
  Render->>Lock: RUnlock()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • gofiber/fiber#4031: Modifies App.ReloadViews() to change how Views.Load() is invoked for mounted apps; this PR's per-Views locking integrates directly with reload scope changes.
  • gofiber/fiber#3876: Introduced ReloadViews() and its error handling; this PR adds synchronization infrastructure around the same method and tests.

Suggested reviewers

  • sixcolors
  • efectn
  • ReneWerner87

Poem

🐰 With locks so fine and mutexes bright,
Views now render in harmony, load and sight,
Read locks dance while writes hold the line,
Concurrency blooms—a synchronous design! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding synchronization between view reloads and template rendering to fix data races.
Description check ✅ Passed The PR description provides clear motivation, describes implementation details across three files, and includes testing information. However, it lacks several required checklist items from the template such as benchmarks, documentation updates, changelog entries, and explicit type-of-change selection.
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-reloadviews-concurrency-issue

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 May 16, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone May 16, 2026
@gaby gaby removed the aardvark label May 16, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.34%. Comparing base (7e4493e) to head (45a3593).
⚠️ Report is 66 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4288      +/-   ##
==========================================
+ Coverage   91.27%   91.34%   +0.07%     
==========================================
  Files         130      132       +2     
  Lines       12797    12969     +172     
==========================================
+ Hits        11680    11847     +167     
- Misses        705      708       +3     
- Partials      412      414       +2     
Flag Coverage Δ
unittests 91.34% <100.00%> (+0.07%) ⬆️

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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 adds synchronization around view engine reloads and template rendering to prevent concurrent Load/Render access from racing at runtime.

Changes:

  • Adds an App.viewsMutex to coordinate view engine access.
  • Locks ReloadViews() with an exclusive lock and rendering with a read lock.
  • Adds a regression test verifying renders wait for an in-flight reload.

Reviewed changes

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

File Description
app.go Adds the view mutex and uses it around Views.Load() in ReloadViews().
res.go Uses the view mutex read lock around Views.Render().
app_test.go Adds a blocking view test for reload/render synchronization.

Comment thread res.go Outdated
Comment thread app.go Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a viewsMutex to the App struct to synchronize view reloading and rendering, ensuring thread safety and preventing rendering during a reload. A new test case was added to verify this blocking behavior. The review feedback suggests improving the robustness of the locking mechanism by using defer within anonymous functions to prevent potential mutex leakage or deadlocks if the view engine panics during Load or Render operations.

Comment thread app.go Outdated
Comment thread res.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.go`:
- Around line 778-783: The manual Lock/Unlock around targetApp.viewsMutex in
ReloadViews is not panic-safe; change it to acquire the lock inside an anonymous
closure (or scope) and call defer targetApp.viewsMutex.Unlock() immediately
after Lock(), so that if targetApp.config.Views.Load() panics or returns an
error the mutex is always released; follow the same pattern used for app.mutex
in this function to ensure unlock happens even on panic.

In `@res.go`:
- Around line 694-699: The read lock on app.viewsMutex is acquired with
app.viewsMutex.RLock() but manually unlocked later, so a panic in
app.config.Views.Render would skip the RUnlock and deadlock ReloadViews; change
to the panic-safe pattern by calling app.viewsMutex.RLock() then immediately
defer app.viewsMutex.RUnlock(), then call app.config.Views.Render(buf, name,
bind, layouts...), and remove the manual RUnlock() before returns; keep the
existing error handling (return fmt.Errorf(...)) so the deferred unlock always
runs even on errors or panics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fdc869f8-bab0-4792-8d01-a04bf23efb43

📥 Commits

Reviewing files that changed from the base of the PR and between 7e4493e and 7240aa7.

📒 Files selected for processing (3)
  • app.go
  • app_test.go
  • res.go

Comment thread app.go Outdated
Comment thread res.go Outdated
@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 16, 2026

@copilot apply changes based on the comments in this thread

Agent-Logs-Url: https://github.com/gofiber/fiber/sessions/57c33969-f896-4fe2-adab-8b430f077fad

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

@copilot apply changes based on the comments in this thread

Addressed in 82e2cad. Both ReloadViews() and Render() now release their view locks via deferred cleanup, and I added regressions covering panics during Load() and Render() so recovered panics no longer strand the mutex.

Copilot finished work on behalf of gaby May 16, 2026 14:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app_test.go (1)

2128-2132: 💤 Low value

Timing-based assertion could be flaky.

The 20ms wait to verify render hasn't entered is susceptible to flakiness under CI load. Consider increasing this timeout or adding a more deterministic synchronization mechanism if flakiness is observed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app_test.go` around lines 2128 - 2132, The timing-based assertion using a
20ms timeout on select (checking view.renderEntered) is flaky; increase the
timeout (e.g., to 200ms) or, better, replace the ad-hoc timing with
deterministic synchronization: have the test control when ReloadViews.Load
proceeds (for example by adding a blocking channel or sync.WaitGroup used by the
mocked ReloadViews.Load) and then assert that view.renderEntered remains empty
until you release that blocker, referencing the test's select that reads
view.renderEntered and the ReloadViews.Load mock to implement the deterministic
block/unblock.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app_test.go`:
- Around line 2128-2132: The timing-based assertion using a 20ms timeout on
select (checking view.renderEntered) is flaky; increase the timeout (e.g., to
200ms) or, better, replace the ad-hoc timing with deterministic synchronization:
have the test control when ReloadViews.Load proceeds (for example by adding a
blocking channel or sync.WaitGroup used by the mocked ReloadViews.Load) and then
assert that view.renderEntered remains empty until you release that blocker,
referencing the test's select that reads view.renderEntered and the
ReloadViews.Load mock to implement the deterministic block/unblock.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a01adc5a-37e3-42c8-b1b3-f847924e03a1

📥 Commits

Reviewing files that changed from the base of the PR and between 7240aa7 and 82e2cad.

📒 Files selected for processing (3)
  • app.go
  • app_test.go
  • res.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • app.go
  • res.go

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 1 comment.

Comment thread app.go Outdated
@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 16, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

@copilot apply changes based on the comments in this thread

Addressed in e68e1e9. The view lock is now keyed by the Config.Views instance instead of by App, so mounted apps that share the same engine also share the same reload/render lock, and I added a sibling mounted-app regression covering that case.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 2 comments.

Comments suppressed due to low confidence (1)

app_test.go:2370

  • This mounted-app regression also uses app.Test while the reload is blocked. During app.Test, startupProcess locks mounted sub-apps via ensureAutoHeadRoutes, so the render request can be waiting on the reloading sub-app's app.mutex rather than on the shared view-engine lock. Pre-start the mounted app and exercise the request handler without the test startup path so the test actually fails when the render RLock is absent.
	go func() {
		_, err := app.Test(httptest.NewRequest(MethodGet, "/b/render", http.NoBody))
		renderDone <- err

Comment thread app.go Outdated
Comment on lines +130 to +136
if viewValue.Type().Comparable() {
key.view = views
} else {
switch viewValue.Kind() {
case reflect.Pointer, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.UnsafePointer:
key.ptr = viewValue.Pointer()
default:
Comment thread app_test.go Outdated
@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 17, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

@copilot apply changes based on the comments in this thread

Addressed in 2d88eb5. Shared view locks are now scoped to the mounted app tree instead of a global cache, and the blocking regressions now prebuild the handler and invoke it directly so they exercise the view lock path rather than app.mutex.

Copilot finished work on behalf of gaby May 17, 2026 17:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
mount.go (1)

48-49: ⚖️ Poor tradeoff

setViewsLockStore called without synchronization.

setViewsLockStore iterates subApp.mountFields.appList without holding any mutex. If subApp is being modified concurrently (unlikely during typical app setup), the map iteration could cause a runtime panic.

The read of app.viewsLockStore is also unsynchronized.

In practice, mount operations occur during single-threaded app setup before Listen(), so this is unlikely to cause issues. Flagging for awareness if concurrent mount patterns are ever supported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mount.go` around lines 48 - 49, The call to
subApp.setViewsLockStore(app.viewsLockStore) accesses subApp.mountFields.appList
and reads app.viewsLockStore without synchronization; update setViewsLockStore
(and any callers) to acquire the appropriate mutex(es) before iterating
subApp.mountFields.appList and before reading app.viewsLockStore to prevent
concurrent map access panics. Specifically, inside setViewsLockStore (or a
wrapper) lock subApp.mountFields' mutex (e.g., mountFields.mu) while iterating
appList, and ensure any read of app.viewsLockStore is performed under app's
read-lock or by copying the reference under app's lock before releasing it.
Ensure locks are held only for the minimal duration to avoid contention.
app.go (1)

118-161: 💤 Low value

Edge case in keying logic for non-comparable struct Views.

If a Views implementation is a non-comparable struct (e.g., contains a slice or map field) but is not itself a pointer/chan/func/map/slice, the key falls through to the default case and only typ is populated. This means all instances of such a type would share a single lock.

This is still safe (no data races), but could cause unnecessary serialization when different view engine instances of the same non-comparable struct type are used.

In practice, most Views implementations are pointers, so this edge case is unlikely to occur. Flagging for awareness.

Possible fix: use unsafe pointer for any addressable non-comparable value
 	} else {
 		switch viewValue.Kind() {
 		case reflect.Pointer, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.UnsafePointer:
 			key.ptr = viewValue.Pointer()
-		default:
+		default:
+			if viewValue.CanAddr() {
+				key.ptr = viewValue.Addr().Pointer()
+			}
 		}
 	}

Note: Interface values passed directly are typically not addressable, so this may not fully resolve the edge case. Consider documenting this limitation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.go` around lines 118 - 161, The keying logic in viewsLockStore.get can
collapse distinct non-comparable struct instances of the same type into one lock
because for non-comparable values that are not pointer/chan/func/map/slice it
only uses typ; fix by populating key.ptr for addressable non-comparable values:
in viewsLockStore.get (function get, types viewsLockKey, viewsLockStore,
variable viewValue/Views) check viewValue.CanAddr() and if true use
viewValue.UnsafeAddr() (or create an addressable copy with
reflect.New(viewValue.Type()) and set it) to set key.ptr so different instances
get different keys; leave the existing pointer/chan/func/map/slice handling and
document the remaining limitation for non-addressable interface values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app.go`:
- Around line 118-161: The keying logic in viewsLockStore.get can collapse
distinct non-comparable struct instances of the same type into one lock because
for non-comparable values that are not pointer/chan/func/map/slice it only uses
typ; fix by populating key.ptr for addressable non-comparable values: in
viewsLockStore.get (function get, types viewsLockKey, viewsLockStore, variable
viewValue/Views) check viewValue.CanAddr() and if true use
viewValue.UnsafeAddr() (or create an addressable copy with
reflect.New(viewValue.Type()) and set it) to set key.ptr so different instances
get different keys; leave the existing pointer/chan/func/map/slice handling and
document the remaining limitation for non-addressable interface values.

In `@mount.go`:
- Around line 48-49: The call to subApp.setViewsLockStore(app.viewsLockStore)
accesses subApp.mountFields.appList and reads app.viewsLockStore without
synchronization; update setViewsLockStore (and any callers) to acquire the
appropriate mutex(es) before iterating subApp.mountFields.appList and before
reading app.viewsLockStore to prevent concurrent map access panics.
Specifically, inside setViewsLockStore (or a wrapper) lock subApp.mountFields'
mutex (e.g., mountFields.mu) while iterating appList, and ensure any read of
app.viewsLockStore is performed under app's read-lock or by copying the
reference under app's lock before releasing it. Ensure locks are held only for
the minimal duration to avoid contention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9ffc95b5-6b4b-407a-8aec-f1485d3cfdce

📥 Commits

Reviewing files that changed from the base of the PR and between e68e1e9 and 2d88eb5.

📒 Files selected for processing (5)
  • app.go
  • app_test.go
  • domain.go
  • mount.go
  • res.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • res.go
  • app_test.go

@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 18, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

@copilot apply changes based on the comments in this thread

Addressed in 45a3593. Shared view locking is now resolved globally by engine identity without mutating mounted app state, and I added a reusable-mount regression so remounting the same sub-app can’t break reload/render synchronization in the original parent.

Copilot finished work on behalf of gaby May 18, 2026 15:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app.go (1)

141-148: 💤 Low value

Non-pointer, non-comparable Views instances of the same type will share a single lock.

When viewValue.Kind() is not pointer-like and viewValue.Type().Comparable() is false, neither key.ptr nor key.view is set. This causes all distinct instances of the same non-comparable value type to share one lock, which may lead to unintended cross-instance blocking.

This is an edge case since most Views implementations are pointers, but consider adding a comment documenting this limitation or handling it explicitly (e.g., by storing the address of the interface data if possible).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.go` around lines 141 - 148, The current switch in app.go leaves key.ptr
and key.view unset for non-pointer, non-comparable Views values causing distinct
instances to share a lock; update the logic in the switch handling for
viewValue.Kind() (and the code that constructs the lock key) to explicitly
handle this case: if viewValue is not pointer-like and not Comparable(), obtain
a unique identifier for the concrete instance (for example use
reflect.Value.CanAddr()/Addr() or fallback to using unsafe.Pointer to the
interface data) and store that address into key.ptr (or another dedicated key
field) so each distinct non-comparable value gets its own lock; alternatively
add a clear comment near viewValue, key.ptr and key.view documenting the
limitation if you decide not to change behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app.go`:
- Around line 141-148: The current switch in app.go leaves key.ptr and key.view
unset for non-pointer, non-comparable Views values causing distinct instances to
share a lock; update the logic in the switch handling for viewValue.Kind() (and
the code that constructs the lock key) to explicitly handle this case: if
viewValue is not pointer-like and not Comparable(), obtain a unique identifier
for the concrete instance (for example use reflect.Value.CanAddr()/Addr() or
fallback to using unsafe.Pointer to the interface data) and store that address
into key.ptr (or another dedicated key field) so each distinct non-comparable
value gets its own lock; alternatively add a clear comment near viewValue,
key.ptr and key.view documenting the limitation if you decide not to change
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 829acd38-d75c-4678-a4d4-55d8e0769327

📥 Commits

Reviewing files that changed from the base of the PR and between 2d88eb5 and 45a3593.

📒 Files selected for processing (3)
  • app.go
  • app_test.go
  • res.go

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

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants