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