Skip to content

Commit

Permalink
fix data race with multiple map writers
Browse files Browse the repository at this point in the history
When the client is initialized with params, the normalizeUser function
will concurrently write the params map. This causes the go runtime to
panic saying "fatal error: concurrent map writes".

This change fixes the data race by always copying the map when
normalizing the user.
  • Loading branch information
sbunce committed Apr 27, 2022
1 parent 2ff49fa commit 3a507bf
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 6 deletions.
10 changes: 4 additions & 6 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,11 @@ func fetchConfig(user User, configName string, t *transport) configResponse {
}

func normalizeUser(user User, options Options) User {
var env map[string]string
if len(options.Environment.Params) > 0 {
env = options.Environment.Params
} else {
env = make(map[string]string)
env := make(map[string]string)
// Copy to avoid data race. We modify the map below.
for k, v := range options.Environment.Params {
env[k] = v
}

if options.Environment.Tier != "" {
env["tier"] = options.Environment.Tier
}
Expand Down
34 changes: 34 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package statsig

import (
"sync"
"testing"
"time"
)

func TestNormalizeUserDataRace(t *testing.T) {
const (
goroutines = 10
duration = time.Second
)
options := Options{
Environment: Environment{
Params: map[string]string{
"foo": "bar",
},
Tier: "awesome",
},
}
start := time.Now()
var wg sync.WaitGroup
wg.Add(goroutines)
for g := 0; g < goroutines; g++ {
go func() {
defer wg.Done()
for time.Since(start) < duration {
normalizeUser(User{UserID: "cruise-llc"}, options)
}
}()
}
wg.Wait()
}

0 comments on commit 3a507bf

Please sign in to comment.