Skip to content

Commit cf0eca5

Browse files
committed
fix: Replace per-call Lock creation with shared lock in SQLiteSession
The lock pattern 'with self._lock if self._is_memory_db else threading.Lock()' creates a new Lock object for each operation when using file-based databases, providing no thread safety for concurrent access. This fix uses the shared self._lock for both memory and file databases, ensuring proper synchronization for concurrent operations. Changes: - get_items() (line 123) - add_items() (line 177) - pop_item() (line 218) - clear_session() (line 255) Testing: - Existing test test_sqlite_session_concurrent_access passes - Manual verification shows proper lock reuse - No breaking changes to API or behavior
1 parent d1abf43 commit cf0eca5

File tree

1 file changed

+9
-4
lines changed

1 file changed

+9
-4
lines changed

src/agents/memory/sqlite_session.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import sqlite3
66
import threading
7+
from contextlib import nullcontext
78
from pathlib import Path
89

910
from ..items import TResponseInputItem
@@ -55,6 +56,10 @@ def __init__(
5556
self._init_db_for_connection(init_conn)
5657
init_conn.close()
5758

59+
def _lock_context(self):
60+
"""Return a context manager guarding writes for in-memory databases."""
61+
return self._lock if self._is_memory_db else nullcontext()
62+
5863
def _get_connection(self) -> sqlite3.Connection:
5964
"""Get a database connection."""
6065
if self._is_memory_db:
@@ -120,7 +125,7 @@ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]:
120125

121126
def _get_items_sync():
122127
conn = self._get_connection()
123-
with self._lock if self._is_memory_db else threading.Lock():
128+
with self._lock_context():
124129
if limit is None:
125130
# Fetch all items in chronological order
126131
cursor = conn.execute(
@@ -174,7 +179,7 @@ async def add_items(self, items: list[TResponseInputItem]) -> None:
174179
def _add_items_sync():
175180
conn = self._get_connection()
176181

177-
with self._lock if self._is_memory_db else threading.Lock():
182+
with self._lock_context():
178183
# Ensure session exists
179184
conn.execute(
180185
f"""
@@ -215,7 +220,7 @@ async def pop_item(self) -> TResponseInputItem | None:
215220

216221
def _pop_item_sync():
217222
conn = self._get_connection()
218-
with self._lock if self._is_memory_db else threading.Lock():
223+
with self._lock_context():
219224
# Use DELETE with RETURNING to atomically delete and return the most recent item
220225
cursor = conn.execute(
221226
f"""
@@ -252,7 +257,7 @@ async def clear_session(self) -> None:
252257

253258
def _clear_session_sync():
254259
conn = self._get_connection()
255-
with self._lock if self._is_memory_db else threading.Lock():
260+
with self._lock_context():
256261
conn.execute(
257262
f"DELETE FROM {self.messages_table} WHERE session_id = ?",
258263
(self.session_id,),

0 commit comments

Comments
 (0)