diff --git a/callbacks/transaction.go b/callbacks/transaction.go index 50887ccce..0f72fe898 100644 --- a/callbacks/transaction.go +++ b/callbacks/transaction.go @@ -8,7 +8,7 @@ func BeginTransaction(db *gorm.DB) { if !db.Config.SkipDefaultTransaction && db.Error == nil { if tx := db.Begin(); tx.Error == nil { db.Statement.ConnPool = tx.Statement.ConnPool - db.InstanceSet("gorm:started_transaction", true) + db.Statement.Settings.Store("gorm:started_transaction", true) } else if tx.Error == gorm.ErrInvalidTransaction { tx.Error = nil } else { @@ -19,7 +19,7 @@ func BeginTransaction(db *gorm.DB) { func CommitOrRollbackTransaction(db *gorm.DB) { if !db.Config.SkipDefaultTransaction { - if _, ok := db.InstanceGet("gorm:started_transaction"); ok { + if _, ok := db.Statement.Settings.Load("gorm:started_transaction"); ok { if db.Error != nil { db.Rollback() } else { diff --git a/tests/transaction_leak_test.go b/tests/transaction_leak_test.go new file mode 100644 index 000000000..b1cddebb1 --- /dev/null +++ b/tests/transaction_leak_test.go @@ -0,0 +1,64 @@ +package tests_test + +import ( + "testing" + + "gorm.io/gorm" + . "gorm.io/gorm/utils/tests" +) + +func TestTransactionLeakWithScopesSession(t *testing.T) { + var users []User + + // Get the underlying sql.DB to check connection stats + sqldb, err := DB.DB() + if err != nil { + t.Error(err) + } + + before := sqldb.Stats().InUse + + // This should not leak transactions - the issue case + result := DB.Scopes(func(s *gorm.DB) *gorm.DB { + return s.Session(&gorm.Session{ + DryRun: true, + }) + }).Where("name = ?", "transaction-leak-test").Find(&users) + + if result.Error != nil { + t.Errorf("Query failed: %v", result.Error) + } + + after := sqldb.Stats().InUse + + if before != after { + t.Errorf("Transaction leak detected: InUse connections before=%d, after=%d", before, after) + } +} + +func TestTransactionLeakWithScopesSessionDelete(t *testing.T) { + // Get the underlying sql.DB to check connection stats + sqldb, err := DB.DB() + if err != nil { + t.Error(err) + } + + before := sqldb.Stats().InUse + + // This should not leak transactions - the more specific case from the playground + result := DB.Scopes(func(s *gorm.DB) *gorm.DB { + return s.Session(&gorm.Session{ + DryRun: true, + }) + }).Where("name = ?", "transaction-leak-test").Delete(&User{}) + + if result.Error != nil { + t.Errorf("Delete failed: %v", result.Error) + } + + after := sqldb.Stats().InUse + + if before != after { + t.Errorf("Transaction leak detected: InUse connections before=%d, after=%d", before, after) + } +} \ No newline at end of file