Fixed ModuleStorage commit race by serializing concurrent operations with an asyncio lock.
CI / Formatting (push) Failing after 6s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 14s
CI / Tests (Python 3.13) (push) Successful in 14s
CI / Tests (Python 3.14) (push) Successful in 12s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s

This commit is contained in:
2026-04-17 14:04:15 -04:00
parent f0e86cb4ad
commit 9c86b8bd75
2 changed files with 85 additions and 20 deletions
+23 -20
View File
@@ -66,6 +66,7 @@ class ModuleStorage:
self._module_name = module_name
self._conn: aiosqlite.Connection | None = None
self._closed = False
self._lock = asyncio.Lock()
self._txn_conn: contextvars.ContextVar[aiosqlite.Connection | None] = (
contextvars.ContextVar(f"_txn_conn_{module_name}", default=None)
)
@@ -114,19 +115,20 @@ class ModuleStorage:
raise RuntimeError(
"transaction() cannot be nested. Already inside an active transaction."
)
conn = await self._ensure_connection()
token = self._txn_conn.set(conn)
self._logger.debug("Explicit transaction started.")
try:
yield self
await conn.commit()
self._logger.debug("Transaction committed.")
except BaseException:
await conn.rollback()
self._logger.debug("Transaction rolled back.")
raise
finally:
self._txn_conn.reset(token)
async with self._lock:
conn = await self._ensure_connection()
token = self._txn_conn.set(conn)
self._logger.debug("Explicit transaction started.")
try:
yield self
await conn.commit()
self._logger.debug("Transaction committed.")
except BaseException:
await conn.rollback()
self._logger.debug("Transaction rolled back.")
raise
finally:
self._txn_conn.reset(token)
async def execute(
self,
@@ -273,13 +275,14 @@ class ModuleStorage:
yield existing
return
conn = await self._ensure_connection()
try:
yield conn
await conn.commit()
except BaseException:
await conn.rollback()
raise
async with self._lock:
conn = await self._ensure_connection()
try:
yield conn
await conn.commit()
except BaseException:
await conn.rollback()
raise
async def _close(self) -> None:
"""Close the connection (internal use by bot)."""