Skip to content

Commit c35e977

Browse files
phernandezclaude
andcommitted
fix: prevent NullPool from destroying in-memory databases on Windows
The critical issue was using NullPool for ALL databases on Windows, including in-memory databases. NullPool closes connections immediately after use, which destroys in-memory SQLite databases, causing "no such table" errors in tests. Changes: - Only use NullPool for FILESYSTEM databases on Windows - In-memory databases now use regular connection pooling to maintain state - Added error handling in _configure_sqlite_connection to prevent PRAGMA failures from breaking connections - Added test to verify in-memory databases don't use NullPool This should fix all Windows test failures while maintaining the benefits of NullPool for filesystem databases on Windows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 9743965 commit c35e977

2 files changed

Lines changed: 62 additions & 27 deletions

File tree

src/basic_memory/db.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,24 @@ def _configure_sqlite_connection(dbapi_conn, enable_wal: bool = True) -> None:
8383
enable_wal: Whether to enable WAL mode (should be False for in-memory databases)
8484
"""
8585
cursor = dbapi_conn.cursor()
86-
# Enable WAL mode for better concurrency (not supported for in-memory databases)
87-
if enable_wal:
88-
cursor.execute("PRAGMA journal_mode=WAL")
89-
# Set busy timeout to handle locked databases
90-
cursor.execute("PRAGMA busy_timeout=10000") # 10 seconds
91-
# Optimize for performance
92-
cursor.execute("PRAGMA synchronous=NORMAL")
93-
cursor.execute("PRAGMA cache_size=-64000") # 64MB cache
94-
cursor.execute("PRAGMA temp_store=MEMORY")
95-
# Windows-specific optimizations
96-
if os.name == "nt":
97-
cursor.execute("PRAGMA locking_mode=NORMAL") # Ensure normal locking on Windows
98-
cursor.close()
86+
try:
87+
# Enable WAL mode for better concurrency (not supported for in-memory databases)
88+
if enable_wal:
89+
cursor.execute("PRAGMA journal_mode=WAL")
90+
# Set busy timeout to handle locked databases
91+
cursor.execute("PRAGMA busy_timeout=10000") # 10 seconds
92+
# Optimize for performance
93+
cursor.execute("PRAGMA synchronous=NORMAL")
94+
cursor.execute("PRAGMA cache_size=-64000") # 64MB cache
95+
cursor.execute("PRAGMA temp_store=MEMORY")
96+
# Windows-specific optimizations
97+
if os.name == "nt":
98+
cursor.execute("PRAGMA locking_mode=NORMAL") # Ensure normal locking on Windows
99+
except Exception as e:
100+
# Log but don't fail - some PRAGMAs may not be supported
101+
logger.warning(f"Failed to configure SQLite connection: {e}")
102+
finally:
103+
cursor.close()
99104

100105

101106
def _create_engine_and_session(
@@ -116,13 +121,19 @@ def _create_engine_and_session(
116121
"isolation_level": None, # Use autocommit mode
117122
}
118123
)
119-
# Use NullPool for Windows to avoid connection pooling issues
120-
engine = create_async_engine(
121-
db_url,
122-
connect_args=connect_args,
123-
poolclass=NullPool, # Disable connection pooling on Windows
124-
echo=False,
125-
)
124+
# Use NullPool for Windows filesystem databases to avoid connection pooling issues
125+
# Important: Do NOT use NullPool for in-memory databases as it will destroy the database
126+
# between connections
127+
if db_type == DatabaseType.FILESYSTEM:
128+
engine = create_async_engine(
129+
db_url,
130+
connect_args=connect_args,
131+
poolclass=NullPool, # Disable connection pooling on Windows
132+
echo=False,
133+
)
134+
else:
135+
# In-memory databases need connection pooling to maintain state
136+
engine = create_async_engine(db_url, connect_args=connect_args)
126137
else:
127138
engine = create_async_engine(db_url, connect_args=connect_args)
128139

@@ -206,13 +217,19 @@ async def engine_session_factory(
206217
"isolation_level": None, # Use autocommit mode
207218
}
208219
)
209-
# Use NullPool for Windows to avoid connection pooling issues
210-
_engine = create_async_engine(
211-
db_url,
212-
connect_args=connect_args,
213-
poolclass=NullPool, # Disable connection pooling on Windows
214-
echo=False,
215-
)
220+
# Use NullPool for Windows filesystem databases to avoid connection pooling issues
221+
# Important: Do NOT use NullPool for in-memory databases as it will destroy the database
222+
# between connections
223+
if db_type == DatabaseType.FILESYSTEM:
224+
_engine = create_async_engine(
225+
db_url,
226+
connect_args=connect_args,
227+
poolclass=NullPool, # Disable connection pooling on Windows
228+
echo=False,
229+
)
230+
else:
231+
# In-memory databases need connection pooling to maintain state
232+
_engine = create_async_engine(db_url, connect_args=connect_args)
216233
else:
217234
_engine = create_async_engine(db_url, connect_args=connect_args)
218235

test-int/test_db_wal_mode.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,21 @@ async def test_regular_pool_on_non_windows(tmp_path):
123123
async with engine_session_factory(db_path, DatabaseType.FILESYSTEM) as (engine, _):
124124
# Engine should NOT be using NullPool on non-Windows
125125
assert not isinstance(engine.pool, NullPool)
126+
127+
128+
@pytest.mark.asyncio
129+
async def test_memory_database_no_null_pool_on_windows(tmp_path):
130+
"""Test that in-memory databases do NOT use NullPool even on Windows.
131+
132+
NullPool closes connections immediately, which destroys in-memory databases.
133+
This test ensures in-memory databases maintain connection pooling.
134+
"""
135+
from basic_memory.db import engine_session_factory, DatabaseType
136+
from sqlalchemy.pool import NullPool
137+
138+
db_path = tmp_path / "test_memory.db"
139+
140+
with patch("basic_memory.db.os.name", "nt"):
141+
async with engine_session_factory(db_path, DatabaseType.MEMORY) as (engine, _):
142+
# In-memory databases should NOT use NullPool on Windows
143+
assert not isinstance(engine.pool, NullPool)

0 commit comments

Comments
 (0)