Skip to content

fix: copy-on-write in updateUserInfo to avoid shared-map data race#317

Closed
ThiagoBauken wants to merge 2 commits into
asternic:mainfrom
ThiagoBauken:fix/userinfo-cache-race
Closed

fix: copy-on-write in updateUserInfo to avoid shared-map data race#317
ThiagoBauken wants to merge 2 commits into
asternic:mainfrom
ThiagoBauken:fix/userinfo-cache-race

Conversation

@ThiagoBauken

Copy link
Copy Markdown
Contributor

Problem

updateUserInfo mutates the map inside Values in place:

func updateUserInfo(values interface{}, field string, value string) interface{} {
    values.(Values).m[field] = value
    return values
}

That map is shared: it is stored in userinfocache and handed to request goroutines through the request context (r.Context().Value("userinfo")). An in-place write therefore races with concurrent readers (Values.Get, called on every authenticated request by the access logger and throughout the handlers). Under load this is a classic fatal error: concurrent map read and map write, which crashes the whole process.

Fix

Make updateUserInfo copy-on-write: build a fresh map, copy the existing entries, set the field, and return a new Values. The shared map is never written. All call sites already use the returned value and persist it via userinfocache.Set, so behaviour is unchanged.

Testing

  • TestUpdateUserInfoCopyOnWrite — deterministic: asserts the source Values is untouched and the returned one carries the update (plus unrelated fields). Verified red→green (fails against the in-place version, passes with the fix).
  • TestUpdateUserInfoConcurrent — hammers updateUserInfo + Get from many goroutines; under go test -race the old version reports the data race, the new one is clean.
  • go build ./... ✅ · go vet ./... ✅ · go test ./...

updateUserInfo mutated values.(Values).m[field] in place. That map is shared:
it lives in userinfocache and is handed to request goroutines via the request
context, so an in-place write races with concurrent readers (Values.Get) and
can crash the process with "concurrent map read and map write". Build a fresh
map and return a new Values instead. All callers already persist the returned
value via userinfocache.Set, so behaviour is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a copy-on-write pattern in updateUserInfo to prevent concurrent map read and write data races, and adds unit tests to verify its correctness and concurrent safety. Feedback on the changes points out a potential runtime panic due to a direct type assertion on values, suggesting the use of a comma-ok assertion to handle nil or unexpected types safely.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread helpers.go Outdated
// and can crash the process with "concurrent map read and map write".
// Build a fresh map and return a new Values; callers persist it via
// userinfocache.Set.
old := values.(Values)

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.

high

Directly type-asserting values.(Values) will panic if values is nil (which can happen if the user info is missing or not yet set in the context/cache) or if it is of an unexpected type. Using the comma-ok type assertion form prevents runtime panics and safely falls back to the zero-value of Values (where m is nil), which is then safely handled by the rest of the function.

Suggested change
old := values.(Values)
old, _ := values.(Values)

values.(Values) would panic on a nil or unexpected value. Use the comma-ok form
so it falls back to the zero Values (nil map), which the copy loop handles
safely. Addresses Gemini Code Assist review on asternic#317.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ThiagoBauken

Copy link
Copy Markdown
Contributor Author

Closing to consolidate my recent PRs into a smaller, focused set that's easier to review — I'll resubmit the changes grouped together. Sorry for the churn, and thanks!

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