fix(dbmanager): resolve deadlock in adapter getter methods

Fixes deadlock caused by calling public methods (Bun, GORM, Native)
while holding a write lock. The public methods attempt to acquire
their own locks, causing the goroutine to freeze.

Solution: Call provider methods directly within the adapter getters
since they already hold the necessary write lock.
This commit is contained in:
2026-01-03 17:26:43 +02:00
parent 70bf0a4be1
commit 99561e10c4

View File

@@ -372,12 +372,20 @@ func (c *sqlConnection) getBunAdapter() (common.Database, error) {
return c.bunAdapter, nil return c.bunAdapter, nil
} }
bunDB, err := c.Bun() // Double-check bunDB exists (while already holding write lock)
if err != nil { if c.bunDB == nil {
return nil, err // Get native connection first
native, err := c.provider.GetNative()
if err != nil {
return nil, NewConnectionError(c.name, "get bun", err)
}
// Create Bun DB wrapping the same sql.DB
dialect := c.getBunDialect()
c.bunDB = bun.NewDB(native, dialect)
} }
c.bunAdapter = database.NewBunAdapter(bunDB) c.bunAdapter = database.NewBunAdapter(c.bunDB)
return c.bunAdapter, nil return c.bunAdapter, nil
} }
@@ -400,12 +408,25 @@ func (c *sqlConnection) getGORMAdapter() (common.Database, error) {
return c.gormAdapter, nil return c.gormAdapter, nil
} }
gormDB, err := c.GORM() // Double-check gormDB exists (while already holding write lock)
if err != nil { if c.gormDB == nil {
return nil, err // Get native connection first
native, err := c.provider.GetNative()
if err != nil {
return nil, NewConnectionError(c.name, "get gorm", err)
}
// Create GORM DB wrapping the same sql.DB
dialector := c.getGORMDialector(native)
db, err := gorm.Open(dialector, &gorm.Config{})
if err != nil {
return nil, NewConnectionError(c.name, "initialize gorm", err)
}
c.gormDB = db
} }
c.gormAdapter = database.NewGormAdapter(gormDB) c.gormAdapter = database.NewGormAdapter(c.gormDB)
return c.gormAdapter, nil return c.gormAdapter, nil
} }
@@ -428,21 +449,31 @@ func (c *sqlConnection) getNativeAdapter() (common.Database, error) {
return c.nativeAdapter, nil return c.nativeAdapter, nil
} }
native, err := c.Native() // Double-check nativeDB exists (while already holding write lock)
if err != nil { if c.nativeDB == nil {
return nil, err if !c.connected {
return nil, ErrConnectionClosed
}
// Get native connection from provider
db, err := c.provider.GetNative()
if err != nil {
return nil, NewConnectionError(c.name, "get native", err)
}
c.nativeDB = db
} }
// Create a native adapter based on database type // Create a native adapter based on database type
switch c.dbType { switch c.dbType {
case DatabaseTypePostgreSQL: case DatabaseTypePostgreSQL:
c.nativeAdapter = database.NewPgSQLAdapter(native) c.nativeAdapter = database.NewPgSQLAdapter(c.nativeDB)
case DatabaseTypeSQLite: case DatabaseTypeSQLite:
// For SQLite, we'll use the PgSQL adapter as it works with standard sql.DB // For SQLite, we'll use the PgSQL adapter as it works with standard sql.DB
c.nativeAdapter = database.NewPgSQLAdapter(native) c.nativeAdapter = database.NewPgSQLAdapter(c.nativeDB)
case DatabaseTypeMSSQL: case DatabaseTypeMSSQL:
// For MSSQL, we'll use the PgSQL adapter as it works with standard sql.DB // For MSSQL, we'll use the PgSQL adapter as it works with standard sql.DB
c.nativeAdapter = database.NewPgSQLAdapter(native) c.nativeAdapter = database.NewPgSQLAdapter(c.nativeDB)
default: default:
return nil, ErrUnsupportedDatabase return nil, ErrUnsupportedDatabase
} }