Skip to content

Commit 9cbcda8

Browse files
authored
fix: concurrent-safe unique data generation (#53)
* fix: concurent map writing issue * fix: test for concurrent write on unique field
1 parent df18fe6 commit 9cbcda8

File tree

2 files changed

+46
-9
lines changed

2 files changed

+46
-9
lines changed

faker.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222

2323
var (
2424
// Unique values are kept in memory so the generator retries if the value already exists
25-
uniqueValues = map[string][]interface{}{}
25+
uniqueValues = &sync.Map{}
2626
)
2727

2828
// Supported tags
@@ -266,7 +266,7 @@ func init() {
266266
// ResetUnique is used to forget generated unique values.
267267
// Call this when you're done generating a dataset.
268268
func ResetUnique() {
269-
uniqueValues = map[string][]interface{}{}
269+
uniqueValues = &sync.Map{}
270270
}
271271

272272
var (
@@ -501,15 +501,16 @@ func getFakedValue(item interface{}, opts *options.Options) (reflect.Value, erro
501501
if retry >= maxRetry {
502502
return reflect.Value{}, fmt.Errorf(fakerErrors.ErrUniqueFailure, reflect.TypeOf(item).Field(i).Name)
503503
}
504-
505504
value := v.Field(i).Interface()
506-
if slice.ContainsValue(uniqueValues[tags.fieldType], value) { // Retry if unique value already found
505+
uniqueVal, _ := uniqueValues.Load(tags.fieldType)
506+
uniqueValArr, _ := uniqueVal.([]interface{})
507+
if slice.ContainsValue(uniqueValArr, value) { // Retry if unique value already found
507508
i--
508509
retry++
509510
continue
510511
}
511512
retry = 0
512-
uniqueValues[tags.fieldType] = append(uniqueValues[tags.fieldType], value)
513+
uniqueValues.Store(tags.fieldType, append(uniqueValArr, value))
513514
} else {
514515
retry = 0
515516
}
@@ -1380,8 +1381,10 @@ func RandomInt(parameters ...int) (p []int, err error) {
13801381
func generateUnique(dataType string, fn func() interface{}) (interface{}, error) {
13811382
for i := 0; i < maxRetry; i++ {
13821383
value := fn()
1383-
if !slice.ContainsValue(uniqueValues[dataType], value) { // Retry if unique value already found
1384-
uniqueValues[dataType] = append(uniqueValues[dataType], value)
1384+
uniqueVal, _ := uniqueValues.Load(dataType)
1385+
uniqueValArr, _ := uniqueVal.([]interface{})
1386+
if !slice.ContainsValue(uniqueValArr, value) { // Retry if unique value already found
1387+
uniqueValues.Store(dataType, append(uniqueValArr, value))
13851388
return value, nil
13861389
}
13871390
}

faker_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,7 +1503,9 @@ func TestUnique(t *testing.T) {
15031503
}
15041504

15051505
found := []interface{}{}
1506-
for _, v := range uniqueValues["word"] {
1506+
uniqueVal, _ := uniqueValues.Load("word")
1507+
uniqueValArr := uniqueVal.([]interface{})
1508+
for _, v := range uniqueValArr {
15071509
for _, f := range found {
15081510
if f == v {
15091511
t.Errorf("expected unique values, found \"%s\" at least twice", v)
@@ -1517,6 +1519,30 @@ func TestUnique(t *testing.T) {
15171519
ResetUnique()
15181520
}
15191521

1522+
func TestUniqueInConcurrentParallel(t *testing.T) {
1523+
t.Parallel()
1524+
1525+
var wg sync.WaitGroup
1526+
wg.Add(3)
1527+
1528+
go func() {
1529+
defer wg.Done()
1530+
Username(options.WithGenerateUniqueValues(true))
1531+
}()
1532+
1533+
go func() {
1534+
defer wg.Done()
1535+
IPv4(options.WithGenerateUniqueValues(true))
1536+
}()
1537+
1538+
go func() {
1539+
defer wg.Done()
1540+
Email(options.WithGenerateUniqueValues(true))
1541+
}()
1542+
1543+
wg.Wait()
1544+
}
1545+
15201546
func TestUniqueReset(t *testing.T) {
15211547
type String struct {
15221548
StringVal string `faker:"word,unique"`
@@ -1531,7 +1557,15 @@ func TestUniqueReset(t *testing.T) {
15311557
}
15321558

15331559
ResetUnique()
1534-
length := len(uniqueValues)
1560+
var getSize = func(m *sync.Map) (count int) {
1561+
m.Range(func(_, _ interface{}) bool {
1562+
count++
1563+
return true
1564+
})
1565+
return
1566+
}
1567+
1568+
length := getSize(uniqueValues)
15351569
if length > 0 {
15361570
t.Errorf("expected empty uniqueValues map, but got a length of %d", length)
15371571
}

0 commit comments

Comments
 (0)