Merge pull request #2396 from emirb/fix-singulartable-race-condition

Fix SingularTable race condition
This commit is contained in:
Emir Beganović 2019-04-14 12:53:45 +04:00 committed by GitHub
commit e3987fd4b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 20 deletions

View File

@ -12,6 +12,7 @@ import (
// DB contains information for current db connection // DB contains information for current db connection
type DB struct { type DB struct {
sync.RWMutex
Value interface{} Value interface{}
Error error Error error
RowsAffected int64 RowsAffected int64
@ -170,7 +171,8 @@ func (s *DB) HasBlockGlobalUpdate() bool {
// SingularTable use singular table by default // SingularTable use singular table by default
func (s *DB) SingularTable(enable bool) { func (s *DB) SingularTable(enable bool) {
modelStructsMap = sync.Map{} s.parent.Lock()
defer s.parent.Unlock()
s.parent.singularTable = enable s.parent.singularTable = enable
} }

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@ -277,6 +278,30 @@ func TestTableName(t *testing.T) {
DB.SingularTable(false) DB.SingularTable(false)
} }
func TestTableNameConcurrently(t *testing.T) {
DB := DB.Model("")
if DB.NewScope(Order{}).TableName() != "orders" {
t.Errorf("Order's table name should be orders")
}
var wg sync.WaitGroup
wg.Add(10)
for i := 1; i <= 10; i++ {
go func(db *gorm.DB) {
DB.SingularTable(true)
wg.Done()
}(DB)
}
wg.Wait()
if DB.NewScope(Order{}).TableName() != "order" {
t.Errorf("Order's singular table name should be order")
}
DB.SingularTable(false)
}
func TestNullValues(t *testing.T) { func TestNullValues(t *testing.T) {
DB.DropTable(&NullValue{}) DB.DropTable(&NullValue{})
DB.AutoMigrate(&NullValue{}) DB.AutoMigrate(&NullValue{})

View File

@ -40,9 +40,11 @@ func (s *ModelStruct) TableName(db *DB) string {
s.defaultTableName = tabler.TableName() s.defaultTableName = tabler.TableName()
} else { } else {
tableName := ToTableName(s.ModelType.Name()) tableName := ToTableName(s.ModelType.Name())
db.parent.RLock()
if db == nil || (db.parent != nil && !db.parent.singularTable) { if db == nil || (db.parent != nil && !db.parent.singularTable) {
tableName = inflection.Plural(tableName) tableName = inflection.Plural(tableName)
} }
db.parent.RUnlock()
s.defaultTableName = tableName s.defaultTableName = tableName
} }
} }
@ -163,7 +165,18 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
} }
// Get Cached model struct // Get Cached model struct
if value, ok := modelStructsMap.Load(reflectType); ok && value != nil { isSingularTable := false
if scope.db != nil && scope.db.parent != nil {
scope.db.parent.RLock()
isSingularTable = scope.db.parent.singularTable
scope.db.parent.RUnlock()
}
hashKey := struct {
singularTable bool
reflectType reflect.Type
}{isSingularTable, reflectType}
if value, ok := modelStructsMap.Load(hashKey); ok && value != nil {
return value.(*ModelStruct) return value.(*ModelStruct)
} }
@ -612,7 +625,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
} }
} }
modelStructsMap.Store(reflectType, &modelStruct) modelStructsMap.Store(hashKey, &modelStruct)
return &modelStruct return &modelStruct
} }

View File

@ -1677,7 +1677,7 @@ func TestPreloadManyToManyCallbacks(t *testing.T) {
lvl := Level1{ lvl := Level1{
Name: "l1", Name: "l1",
Level2s: []Level2{ Level2s: []Level2{
Level2{Name: "l2-1"}, Level2{Name: "l2-2"}, {Name: "l2-1"}, {Name: "l2-2"},
}, },
} }
DB.Save(&lvl) DB.Save(&lvl)

View File

@ -83,7 +83,7 @@ build:
code: | code: |
cd $WERCKER_SOURCE_DIR cd $WERCKER_SOURCE_DIR
go version go version
go get -t ./... go get -t -v ./...
# Build the project # Build the project
- script: - script:
@ -95,54 +95,54 @@ build:
- script: - script:
name: test sqlite name: test sqlite
code: | code: |
go test ./... go test -race -v ./...
- script: - script:
name: test mariadb name: test mariadb
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mariadb:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mariadb:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.7 name: test mysql5.7
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql57:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql57:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.6 name: test mysql5.6
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql56:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql56:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.5 name: test mysql5.5
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql55:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql55:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test postgres name: test postgres
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres96 name: test postgres96
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres96 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres96 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres95 name: test postgres95
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres95 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres95 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres94 name: test postgres94
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres94 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres94 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres93 name: test postgres93
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres93 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres93 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test mssql name: test mssql
code: | code: |
GORM_DIALECT=mssql GORM_DSN="sqlserver://gorm:LoremIpsum86@mssql:1433?database=gorm" go test ./... GORM_DIALECT=mssql GORM_DSN="sqlserver://gorm:LoremIpsum86@mssql:1433?database=gorm" go test -race ./...