Skip to content

fix(uaparser): clone the shared default RegexDefinitions in New - #102

Merged
dgoldstein0 merged 1 commit into
ua-parser:masterfrom
SAY-5:fix/mustcompile-race
May 29, 2026
Merged

dgoldstein0 merged 1 commit into
ua-parser:masterfrom
SAY-5:fix/mustcompile-race

Conversation

@SAY-5

@SAY-5 SAY-5 commented May 19, 2026

Copy link
Copy Markdown
Contributor

The cached default returned by defaultRegexesDefinitions is a sync.OnceValue of a RegexDefinitions value, but its UA/OS/Device fields are slices of pointers shared across every New call, so mustCompile writes p.Reg on the same elements from two goroutines initializing in parallel. Cloning the cached default in New gives each parser its own pointer set, and a new -race test exercises 16 concurrent New() calls.

Closes #100

The cached default returned by defaultRegexesDefinitions is a sync.OnceValue
of a RegexDefinitions value, but its UA/OS/Device fields are slices of
pointers shared across every New call. mustCompile then writes p.Reg on
those shared pointers, racing whenever two parsers initialize concurrently
(seen via statsig-io/go-sdk spawning a parser per client).
Closes ua-parser#100

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>

@dgoldstein0 dgoldstein0 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.

assuming this passes tests the fix looks good. Took me a moment to determine there's not an extra copy of regexesDefinitions as .Clone() takes and returns pointers.

any chance of wiring up -race to our CI? https://github.com/ua-parser/uap-go/blob/0623ea3aeb18d27815c335a34069938c7c1ffa17/.github/workflows/ci.yml - not sure if it's best as a separate step or as part of the existing "Run tests". If you want that can be a separate PR, or just include in this one.

Comment thread uaparser/race_test.go
Comment on lines +8 to +11
// TestNewConcurrent_NoRaceOnDefaultRegexDefinitions reproduces the data race
// reported in issue #100. Without the fix, multiple goroutines calling New()
// with the default cached RegexDefinitions racing on the shared []*uaParser
// pointers triggers a race detector failure under -race.

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.

hm, I think our CI doesn't run with -race - perhaps that could be easily added? That would guarantee that we stay race-free much better than relying on contributors to manually run tests with -race. Any chance of adding that to this PR?

@dgoldstein0

Copy link
Copy Markdown
Contributor

... well I suppose a fix is better than nothing, so I'm going to merge as is and file a task for adding a -race version to our CI - feel free to contribute the PR for that.

@dgoldstein0
dgoldstein0 merged commit 17c35e6 into ua-parser:master May 29, 2026
3 checks passed
@dgoldstein0

Copy link
Copy Markdown
Contributor

filed that followup as #104

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.

Data race in (*Parser).mustCompile() when multiple parsers initialise concurrently

2 participants