diabolical

This commit is contained in:
pacnpal
2024-11-18 05:17:37 +00:00
parent fc06e54d8a
commit df9099f2c8
75 changed files with 744 additions and 719 deletions

View File

@@ -10,24 +10,29 @@ import threading
from queue import Queue, Empty
from datetime import datetime
#try:
# Try relative imports first
from ..utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
#except ImportError:
# Fall back to absolute imports if relative imports fail
# from videoarchiver.utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
# try:
# Try relative imports first
from utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
# except ImportError:
# Fall back to absolute imports if relative imports fail
# from videoarchiver.utils.exceptions import DatabaseError, ErrorContext, ErrorSeverity
logger = logging.getLogger("DBConnectionManager")
class ConnectionState(Enum):
"""Connection states"""
AVAILABLE = auto()
IN_USE = auto()
CLOSED = auto()
ERROR = auto()
class ConnectionStatus(TypedDict):
"""Type definition for connection status"""
state: str
created_at: str
last_used: str
@@ -36,8 +41,10 @@ class ConnectionStatus(TypedDict):
pool_size: int
available_connections: int
class ConnectionMetrics(TypedDict):
"""Type definition for connection metrics"""
total_connections: int
active_connections: int
idle_connections: int
@@ -46,6 +53,7 @@ class ConnectionMetrics(TypedDict):
failed_transactions: int
average_transaction_time: float
class ConnectionInfo:
"""Tracks connection information"""
@@ -73,6 +81,7 @@ class ConnectionInfo:
return 0.0
return self.total_transaction_time / self.transaction_count
class DatabaseConnectionManager:
"""Manages SQLite database connections and connection pooling"""
@@ -83,11 +92,11 @@ class DatabaseConnectionManager:
def __init__(self, db_path: Path, pool_size: int = DEFAULT_POOL_SIZE) -> None:
"""
Initialize the connection manager.
Args:
db_path: Path to the SQLite database file
pool_size: Maximum number of connections in the pool
Raises:
DatabaseError: If initialization fails
"""
@@ -97,14 +106,14 @@ class DatabaseConnectionManager:
self._connection_info: Dict[int, ConnectionInfo] = {}
self._local = threading.local()
self._lock = threading.Lock()
# Initialize connection pool
self._initialize_pool()
def _initialize_pool(self) -> None:
"""
Initialize the connection pool.
Raises:
DatabaseError: If pool initialization fails
"""
@@ -123,17 +132,17 @@ class DatabaseConnectionManager:
"ConnectionManager",
"initialize_pool",
{"pool_size": self.pool_size},
ErrorSeverity.CRITICAL
)
ErrorSeverity.CRITICAL,
),
)
def _create_connection(self) -> Optional[sqlite3.Connection]:
"""
Create a new database connection with proper settings.
Returns:
New database connection or None if creation fails
Raises:
DatabaseError: If connection creation fails
"""
@@ -141,23 +150,23 @@ class DatabaseConnectionManager:
conn = sqlite3.connect(
self.db_path,
detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
timeout=self.CONNECTION_TIMEOUT
timeout=self.CONNECTION_TIMEOUT,
)
# Enable foreign keys
conn.execute("PRAGMA foreign_keys = ON")
# Set journal mode to WAL for better concurrency
conn.execute("PRAGMA journal_mode = WAL")
# Set synchronous mode to NORMAL for better performance
conn.execute("PRAGMA synchronous = NORMAL")
# Enable extended result codes for better error handling
conn.execute("PRAGMA extended_result_codes = ON")
return conn
except sqlite3.Error as e:
error = f"Failed to create database connection: {str(e)}"
logger.error(error, exc_info=True)
@@ -167,18 +176,18 @@ class DatabaseConnectionManager:
"ConnectionManager",
"create_connection",
{"path": str(self.db_path)},
ErrorSeverity.HIGH
)
ErrorSeverity.HIGH,
),
)
@contextmanager
def get_connection(self) -> Generator[sqlite3.Connection, None, None]:
"""
Get a database connection from the pool.
Yields:
Database connection
Raises:
DatabaseError: If unable to get a connection
"""
@@ -186,7 +195,7 @@ class DatabaseConnectionManager:
start_time = datetime.utcnow()
try:
# Check if we have a transaction-bound connection
conn = getattr(self._local, 'transaction_connection', None)
conn = getattr(self._local, "transaction_connection", None)
if conn is not None:
yield conn
return
@@ -204,8 +213,8 @@ class DatabaseConnectionManager:
"ConnectionManager",
"get_connection",
None,
ErrorSeverity.HIGH
)
ErrorSeverity.HIGH,
),
)
# Update connection info
@@ -229,32 +238,31 @@ class DatabaseConnectionManager:
raise DatabaseError(
error,
context=ErrorContext(
"ConnectionManager",
"get_connection",
None,
ErrorSeverity.HIGH
)
"ConnectionManager", "get_connection", None, ErrorSeverity.HIGH
),
)
finally:
if conn and not hasattr(self._local, 'transaction_connection'):
if conn and not hasattr(self._local, "transaction_connection"):
try:
conn.rollback() # Reset connection state
self._connection_pool.put(conn)
# Update connection info
if id(conn) in self._connection_info:
conn_info = self._connection_info[id(conn)]
conn_info.state = ConnectionState.AVAILABLE
duration = (datetime.utcnow() - start_time).total_seconds()
conn_info.total_transaction_time += duration
except Exception as e:
logger.error(f"Error returning connection to pool: {e}")
try:
conn.close()
if id(conn) in self._connection_info:
self._connection_info[id(conn)].state = ConnectionState.CLOSED
self._connection_info[id(conn)].state = (
ConnectionState.CLOSED
)
except Exception:
pass
@@ -262,22 +270,19 @@ class DatabaseConnectionManager:
def transaction(self) -> Generator[sqlite3.Connection, None, None]:
"""
Start a database transaction.
Yields:
Database connection for the transaction
Raises:
DatabaseError: If unable to start transaction
"""
if hasattr(self._local, 'transaction_connection'):
if hasattr(self._local, "transaction_connection"):
raise DatabaseError(
"Nested transactions are not supported",
context=ErrorContext(
"ConnectionManager",
"transaction",
None,
ErrorSeverity.HIGH
)
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
),
)
conn = None
@@ -293,11 +298,8 @@ class DatabaseConnectionManager:
raise DatabaseError(
"Failed to create database connection",
context=ErrorContext(
"ConnectionManager",
"transaction",
None,
ErrorSeverity.HIGH
)
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
),
)
# Update connection info
@@ -330,42 +332,41 @@ class DatabaseConnectionManager:
raise DatabaseError(
error,
context=ErrorContext(
"ConnectionManager",
"transaction",
None,
ErrorSeverity.HIGH
)
"ConnectionManager", "transaction", None, ErrorSeverity.HIGH
),
)
finally:
if conn:
try:
# Remove thread-local binding
delattr(self._local, 'transaction_connection')
delattr(self._local, "transaction_connection")
# Return connection to pool
self._connection_pool.put(conn)
# Update connection info
if id(conn) in self._connection_info:
conn_info = self._connection_info[id(conn)]
conn_info.state = ConnectionState.AVAILABLE
duration = (datetime.utcnow() - start_time).total_seconds()
conn_info.total_transaction_time += duration
except Exception as e:
logger.error(f"Error cleaning up transaction: {e}")
try:
conn.close()
if id(conn) in self._connection_info:
self._connection_info[id(conn)].state = ConnectionState.CLOSED
self._connection_info[id(conn)].state = (
ConnectionState.CLOSED
)
except Exception:
pass
def close_all(self) -> None:
"""
Close all connections in the pool.
Raises:
DatabaseError: If cleanup fails
"""
@@ -377,7 +378,9 @@ class DatabaseConnectionManager:
try:
conn.close()
if id(conn) in self._connection_info:
self._connection_info[id(conn)].state = ConnectionState.CLOSED
self._connection_info[id(conn)].state = (
ConnectionState.CLOSED
)
except Exception as e:
logger.error(f"Error closing connection: {e}")
except Empty:
@@ -388,79 +391,74 @@ class DatabaseConnectionManager:
raise DatabaseError(
error,
context=ErrorContext(
"ConnectionManager",
"close_all",
None,
ErrorSeverity.HIGH
)
"ConnectionManager", "close_all", None, ErrorSeverity.HIGH
),
)
def get_status(self) -> ConnectionStatus:
"""
Get current connection manager status.
Returns:
Connection status information
"""
active_connections = sum(
1 for info in self._connection_info.values()
1
for info in self._connection_info.values()
if info.state == ConnectionState.IN_USE
)
return ConnectionStatus(
state="healthy" if active_connections < self.pool_size else "exhausted",
created_at=min(
info.created_at.isoformat()
for info in self._connection_info.values()
info.created_at.isoformat() for info in self._connection_info.values()
),
last_used=max(
info.last_used.isoformat()
for info in self._connection_info.values()
info.last_used.isoformat() for info in self._connection_info.values()
),
error=None,
transaction_count=sum(
info.transaction_count
for info in self._connection_info.values()
info.transaction_count for info in self._connection_info.values()
),
pool_size=self.pool_size,
available_connections=self.pool_size - active_connections
available_connections=self.pool_size - active_connections,
)
def get_metrics(self) -> ConnectionMetrics:
"""
Get connection metrics.
Returns:
Connection metrics information
"""
total_transactions = sum(
info.transaction_count
for info in self._connection_info.values()
)
total_errors = sum(
info.error_count
for info in self._connection_info.values()
info.transaction_count for info in self._connection_info.values()
)
total_errors = sum(info.error_count for info in self._connection_info.values())
total_time = sum(
info.total_transaction_time
for info in self._connection_info.values()
info.total_transaction_time for info in self._connection_info.values()
)
return ConnectionMetrics(
total_connections=len(self._connection_info),
active_connections=sum(
1 for info in self._connection_info.values()
1
for info in self._connection_info.values()
if info.state == ConnectionState.IN_USE
),
idle_connections=sum(
1 for info in self._connection_info.values()
1
for info in self._connection_info.values()
if info.state == ConnectionState.AVAILABLE
),
failed_connections=sum(
1 for info in self._connection_info.values()
1
for info in self._connection_info.values()
if info.state == ConnectionState.ERROR
),
total_transactions=total_transactions,
failed_transactions=total_errors,
average_transaction_time=total_time / total_transactions if total_transactions > 0 else 0.0
average_transaction_time=(
total_time / total_transactions if total_transactions > 0 else 0.0
),
)