Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locked when i try to append association in transaction mode via gorm/gen #1070

Open
alan890104 opened this issue Jan 25, 2024 · 1 comment · May be fixed by #1239
Open

Locked when i try to append association in transaction mode via gorm/gen #1070

alan890104 opened this issue Jan 25, 2024 · 1 comment · May be fixed by #1239

Comments

@alan890104
Copy link

GORM Playground Link

It's really hard to simulate by gen.go in gorm playground repo, but this issue is indeed critical. Thanks to @peterxcli for discovering this bug.

Description

First, we define User and Role in model package. A user has many roles, and a role can assign to multiple users, so they are many2many relation.

type User struct {
	ID    string  `json:"id" gorm:"primaryKey"`
	Name  string  `json:"name" gorm:"unique"`
	Roles []*Role `json:"roles" gorm:"many2many:user_roles;"`
}

func (u *User) BeforeCreate(db *gorm.DB) error {
	if u.ID == "" {
		u.ID = uuid.NewString()
	}
	return nil
}

type Role struct {
	ID     string `json:"id" gorm:"primaryKey"`
	Policy string `json:"policy" gorm:"unique"`
}

func (r *Role) BeforeCreate(db *gorm.DB) error {
	if r.ID == "" {
		r.ID = uuid.NewString()
	}
	return nil
}

And then, we generate the query for these two structs

func main() {
	g := gen.NewGenerator(gen.Config{
		OutPath: "./query",
		Mode:    gen.WithDefaultQuery | gen.WithQueryInterface,
	})

	gormdb, err := gorm.Open(mysql.Open("myuser:mypassword@(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"))
	if err != nil {
		log.Fatal(err)
	}
	g.UseDB(gormdb)
	g.ApplyBasic(model.User{}, model.Role{})
	g.Execute()

	// auto migrate
	gormdb.AutoMigrate(&model.User{}, &model.Role{})
}

Next, in our main function, we insert the default rules

        db, err := gorm.Open(mysql.Open("myuser:mypassword@(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"))
	if err != nil {
		log.Fatal(err)
	}
	if err = db.AutoMigrate(&model.User{}, &model.Role{}); err != nil {
		log.Fatal(err)
	}

	// create default roles
	roles := []model.Role{
		{Policy: "admin"},
		{Policy: "user"},
		{Policy: "guest"},
	}
	if err = db.CreateInBatches(&roles, 100).Error; err != nil {
		log.Println("roles already exist")
	}

However, if i try to use a transaction to append roles to a single user, the transaction will lock until timeout

	q := query.Use(db)
	if err := q.Transaction(func(tx *query.Query) error {
		user := model.User{
			Name: "alan",
		}
		if err := tx.User.WithContext(context.Background()).Create(&user); err != nil {
			return err
		}
		log.Println("User created with id:", user.ID)
		// append roles
		roleIDs := []string{roles[0].ID, roles[1].ID}
		userRoles := make([]*model.Role, len(roleIDs))
		for i, id := range roleIDs {
			userRoles[i] = &model.Role{ID: id}
		}
		if err := q.User.Roles.Model(&user).Append(userRoles...); err != nil {
			return err
		}
		return nil
	}); err != nil {
		log.Fatal(err)
	}
@bkmeneguello
Copy link

I have similar issues with transaction. I'm using sqlite for unit tests and have SetMaxOpenConns(1). When I try to use gen.Transaction it locks, but gorm.DB.Transaction works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants