Skip to content

Commit e359fee

Browse files
authored
Merge branch 'master' into master
2 parents 2ee239a + 2586a05 commit e359fee

File tree

3 files changed

+116
-2
lines changed

3 files changed

+116
-2
lines changed

callback.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *
101101
}
102102
}
103103

104+
if cp.logger != nil {
105+
// note cp.logger will be nil during the default gorm callback registrations
106+
// as they occur within init() blocks. However, any user-registered callbacks
107+
// will happen after cp.logger exists (as the default logger or user-specified).
108+
cp.logger.Print("info", fmt.Sprintf("[info] registering callback `%v` from %v", callbackName, fileWithLineNum()))
109+
}
104110
cp.name = callbackName
105111
cp.processor = &callback
106112
cp.parent.processors = append(cp.parent.processors, cp)

model_struct.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ var DefaultTableNameHandler = func(db *DB, defaultTableName string) string {
1717
return defaultTableName
1818
}
1919

20+
// lock for mutating global cached model metadata
21+
var structsLock sync.Mutex
22+
23+
// global cache of model metadata
2024
var modelStructsMap sync.Map
2125

2226
// ModelStruct model definition
@@ -419,8 +423,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
419423
for idx, foreignKey := range foreignKeys {
420424
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
421425
if associationField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); associationField != nil {
422-
// source foreign keys
426+
// mark field as foreignkey, use global lock to avoid race
427+
structsLock.Lock()
423428
foreignField.IsForeignKey = true
429+
structsLock.Unlock()
430+
431+
// association foreign keys
424432
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)
425433
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName)
426434

@@ -523,8 +531,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
523531
for idx, foreignKey := range foreignKeys {
524532
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
525533
if scopeField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); scopeField != nil {
534+
// mark field as foreignkey, use global lock to avoid race
535+
structsLock.Lock()
526536
foreignField.IsForeignKey = true
527-
// source foreign keys
537+
structsLock.Unlock()
538+
539+
// association foreign keys
528540
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, scopeField.Name)
529541
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, scopeField.DBName)
530542

@@ -582,7 +594,10 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
582594
for idx, foreignKey := range foreignKeys {
583595
if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil {
584596
if associationField := getForeignField(associationForeignKeys[idx], toFields); associationField != nil {
597+
// mark field as foreignkey, use global lock to avoid race
598+
structsLock.Lock()
585599
foreignField.IsForeignKey = true
600+
structsLock.Unlock()
586601

587602
// association foreign keys
588603
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)

model_struct_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package gorm_test
2+
3+
import (
4+
"sync"
5+
"testing"
6+
7+
"github.com/jinzhu/gorm"
8+
)
9+
10+
type ModelA struct {
11+
gorm.Model
12+
Name string
13+
14+
ModelCs []ModelC `gorm:"foreignkey:OtherAID"`
15+
}
16+
17+
type ModelB struct {
18+
gorm.Model
19+
Name string
20+
21+
ModelCs []ModelC `gorm:"foreignkey:OtherBID"`
22+
}
23+
24+
type ModelC struct {
25+
gorm.Model
26+
Name string
27+
28+
OtherAID uint64
29+
OtherA *ModelA `gorm:"foreignkey:OtherAID"`
30+
OtherBID uint64
31+
OtherB *ModelB `gorm:"foreignkey:OtherBID"`
32+
}
33+
34+
// This test will try to cause a race condition on the model's foreignkey metadata
35+
func TestModelStructRaceSameModel(t *testing.T) {
36+
// use a WaitGroup to execute as much in-sync as possible
37+
// it's more likely to hit a race condition than without
38+
n := 32
39+
start := sync.WaitGroup{}
40+
start.Add(n)
41+
42+
// use another WaitGroup to know when the test is done
43+
done := sync.WaitGroup{}
44+
done.Add(n)
45+
46+
for i := 0; i < n; i++ {
47+
go func() {
48+
start.Wait()
49+
50+
// call GetStructFields, this had a race condition before we fixed it
51+
DB.NewScope(&ModelA{}).GetStructFields()
52+
53+
done.Done()
54+
}()
55+
56+
start.Done()
57+
}
58+
59+
done.Wait()
60+
}
61+
62+
// This test will try to cause a race condition on the model's foreignkey metadata
63+
func TestModelStructRaceDifferentModel(t *testing.T) {
64+
// use a WaitGroup to execute as much in-sync as possible
65+
// it's more likely to hit a race condition than without
66+
n := 32
67+
start := sync.WaitGroup{}
68+
start.Add(n)
69+
70+
// use another WaitGroup to know when the test is done
71+
done := sync.WaitGroup{}
72+
done.Add(n)
73+
74+
for i := 0; i < n; i++ {
75+
i := i
76+
go func() {
77+
start.Wait()
78+
79+
// call GetStructFields, this had a race condition before we fixed it
80+
if i%2 == 0 {
81+
DB.NewScope(&ModelA{}).GetStructFields()
82+
} else {
83+
DB.NewScope(&ModelB{}).GetStructFields()
84+
}
85+
86+
done.Done()
87+
}()
88+
89+
start.Done()
90+
}
91+
92+
done.Wait()
93+
}

0 commit comments

Comments
 (0)