Recover DB connection automatically after network returns

This commit is contained in:
Mikhail Chusavitin
2026-02-04 11:43:31 +03:00
parent d094d39427
commit f42b850734

View File

@@ -32,14 +32,14 @@ type ConnectionStatus struct {
// ConnectionManager manages database connections with thread-safety and connection pooling // ConnectionManager manages database connections with thread-safety and connection pooling
type ConnectionManager struct { type ConnectionManager struct {
localDB *localdb.LocalDB // for getting DSN from settings localDB *localdb.LocalDB // for getting DSN from settings
mu sync.RWMutex // protects db and state mu sync.RWMutex // protects db and state
db *gorm.DB // current connection (nil if not connected) db *gorm.DB // current connection (nil if not connected)
lastError error // last connection error lastError error // last connection error
lastCheck time.Time // time of last check/attempt lastCheck time.Time // time of last check/attempt
connectTimeout time.Duration // timeout for connection (default: 5s) connectTimeout time.Duration // timeout for connection (default: 5s)
pingInterval time.Duration // minimum interval between pings (default: 30s) pingInterval time.Duration // minimum interval between pings (default: 30s)
reconnectCooldown time.Duration // pause after failed attempt (default: 10s) reconnectCooldown time.Duration // pause after failed attempt (default: 10s)
} }
// NewConnectionManager creates a new ConnectionManager instance // NewConnectionManager creates a new ConnectionManager instance
@@ -94,6 +94,8 @@ func (cm *ConnectionManager) GetDB() (*gorm.DB, error) {
// Attempt to connect // Attempt to connect
err := cm.connect() err := cm.connect()
if err != nil { if err != nil {
// Drop stale handle so callers don't treat it as an active connection.
cm.db = nil
cm.lastError = err cm.lastError = err
cm.lastCheck = time.Now() cm.lastCheck = time.Now()
return nil, err return nil, err
@@ -147,23 +149,27 @@ func (cm *ConnectionManager) connect() error {
return nil return nil
} }
// IsOnline checks if the database is currently connected and responsive // IsOnline checks if the database is currently connected and responsive.
// Does not attempt to reconnect, only checks current state with caching // If disconnected, it tries to reconnect (respecting cooldowns in GetDB).
func (cm *ConnectionManager) IsOnline() bool { func (cm *ConnectionManager) IsOnline() bool {
cm.mu.RLock() cm.mu.RLock()
if cm.db == nil { isDisconnected := cm.db == nil
cm.mu.RUnlock() lastErr := cm.lastError
return false checkedRecently := time.Since(cm.lastCheck) < cm.pingInterval
}
// If we've checked recently, return cached result
if time.Since(cm.lastCheck) < cm.pingInterval {
cm.mu.RUnlock()
return true
}
cm.mu.RUnlock() cm.mu.RUnlock()
// Need to perform actual ping // Try reconnect in disconnected state.
if isDisconnected {
_, err := cm.GetDB()
return err == nil
}
// If we've checked recently, return cached result.
if checkedRecently {
return lastErr == nil
}
// Need to perform actual ping.
cm.mu.Lock() cm.mu.Lock()
defer cm.mu.Unlock() defer cm.mu.Unlock()
@@ -282,7 +288,7 @@ func extractHostFromDSN(dsn string) string {
} }
if parenEnd != -1 { if parenEnd != -1 {
// Extract host:port part between tcp( and ) // Extract host:port part between tcp( and )
hostPort := dsn[tcpStart+1:parenEnd] hostPort := dsn[tcpStart+1 : parenEnd]
return hostPort return hostPort
} }
} }
@@ -317,7 +323,7 @@ func extractHostFromDSN(dsn string) string {
} }
if parenEnd != -1 { if parenEnd != -1 {
hostPort := dsn[parenStart+1:parenEnd] hostPort := dsn[parenStart+1 : parenEnd]
return hostPort return hostPort
} }
} }
@@ -325,4 +331,4 @@ func extractHostFromDSN(dsn string) string {
// If we can't parse it, return empty string // If we can't parse it, return empty string
return "" return ""
} }