d54ed1440c
Add MEMORY_ENGINES_LOCK to engine_cache.py and wrap the check-and-set in UnitOfWork.engine with it to prevent concurrent threads from creating duplicate in-memory SQLite engines. Also fix cache-hit bug where self._engine was never assigned on a cache hit. Closes #7566
36 lines
1.6 KiB
Gherkin
36 lines
1.6 KiB
Gherkin
Feature: Thread-safe engine cache prevents TOCTOU race on MEMORY_ENGINES
|
|
As a developer relying on the in-memory SQLite engine cache
|
|
I want MEMORY_ENGINES access to be protected by a lock
|
|
So that concurrent threads cannot create duplicate engine instances for the same URL
|
|
|
|
# --- Lock Export ---
|
|
|
|
Scenario: MEMORY_ENGINES_LOCK is exported from engine_cache module
|
|
Given the engine cache module is imported
|
|
Then MEMORY_ENGINES_LOCK should be accessible from the engine_cache module
|
|
And MEMORY_ENGINES_LOCK should be a threading.Lock instance
|
|
|
|
# --- Cache-Hit Regression ---
|
|
|
|
Scenario: A second UnitOfWork for the same in-memory URL reuses the cached engine
|
|
Given the engine cache is cleared
|
|
And a UnitOfWork is created for "sqlite:///:memory:"
|
|
When a second UnitOfWork is created for "sqlite:///:memory:"
|
|
Then both UnitOfWork instances should share the same engine instance
|
|
|
|
# --- Lock Acquisition ---
|
|
|
|
Scenario: The engine property acquires MEMORY_ENGINES_LOCK before creating an engine
|
|
Given the engine cache is cleared
|
|
And MEMORY_ENGINES_LOCK acquisition is tracked
|
|
When a UnitOfWork engine is accessed for "sqlite:///:memory:"
|
|
Then MEMORY_ENGINES_LOCK should have been acquired at least once
|
|
|
|
# --- Concurrent Access ---
|
|
|
|
Scenario: Concurrent engine creation for the same URL yields one engine instance
|
|
Given the engine cache is cleared
|
|
When 10 threads each access the engine cache for "sqlite:///:memory:"
|
|
Then all threads should have received the same engine instance
|
|
And MEMORY_ENGINES should contain exactly one entry for "sqlite:///:memory:"
|