From 9c91d6492f227d9db172e634fae7fff099399c23 Mon Sep 17 00:00:00 2001 From: wsimon1982 Date: Mon, 9 Mar 2026 19:34:15 +0100 Subject: [PATCH] fix: close SQLite connections on exception in calculate_table_hash and _get_count Fixes #767 Both calculate_table_hash() and _get_count() in RustChainSyncManager opened a SQLite connection with _get_connection() but relied on reaching conn.close() at the bottom of the function. Any exception raised by cursor.execute() or fetchall() / fetchone() would bypass conn.close(), silently leaking the connection. SQLite limits the number of open connections per process. Under sustained error conditions (e.g. disk I/O errors, locked DB) repeated calls would exhaust that limit and make the node unable to open any new database connections. Wrap all cursor operations in try/finally blocks to guarantee conn.close() is always called, mirroring the pattern already used in apply_sync_payload and _load_table_schema. --- node/rustchain_sync.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/node/rustchain_sync.py b/node/rustchain_sync.py index e971746eb..ef7848cec 100644 --- a/node/rustchain_sync.py +++ b/node/rustchain_sync.py @@ -99,21 +99,22 @@ def calculate_table_hash(self, table_name: str) -> str: if not schema: return "" - conn = self._get_connection() - cursor = conn.cursor() - pk = schema["pk"] - cursor.execute(f"SELECT * FROM {table_name} ORDER BY {pk} ASC") - rows = cursor.fetchall() + conn = self._get_connection() + try: + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM {table_name} ORDER BY {pk} ASC") + rows = cursor.fetchall() - hasher = hashlib.sha256() - for row in rows: - row_dict = dict(row) - row_str = json.dumps(row_dict, sort_keys=True, separators=(",", ":")) - hasher.update(row_str.encode()) + hasher = hashlib.sha256() + for row in rows: + row_dict = dict(row) + row_str = json.dumps(row_dict, sort_keys=True, separators=(",", ":")) + hasher.update(row_str.encode()) - conn.close() - return hasher.hexdigest() + return hasher.hexdigest() + finally: + conn.close() def get_merkle_root(self) -> str: """Generates a master Merkle root hash for all synced tables.""" @@ -262,8 +263,10 @@ def _get_count(self, table_name: str) -> int: if table_name not in self.SYNC_TABLES: return 0 conn = self._get_connection() - cursor = conn.cursor() - cursor.execute(f"SELECT COUNT(*) FROM {table_name}") - count = cursor.fetchone()[0] - conn.close() - return int(count) + try: + cursor = conn.cursor() + cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + count = cursor.fetchone()[0] + return int(count) + finally: + conn.close()