Skip to content

Commit

Permalink
Add SQLiteCache.__del__() method to force connection close if used wi…
Browse files Browse the repository at this point in the history
…thout a contextmanager
  • Loading branch information
JWCook committed Oct 6, 2023
1 parent d72bafa commit 8ef7b80
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 2 deletions.
14 changes: 14 additions & 0 deletions aiohttp_client_cache/backends/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ async def _init_db(self):
)
return self._connection

def __del__(self):
"""If the aiosqlite connection is still open when this object is deleted, force its thread
to close by emptying its internal queue and setting its ``_running`` flag to ``False``.
This is basically a last resort to avoid hanging the application if this backend is used
without the CachedSession contextmanager.
Note: Since this uses internal attributes, it has the potential to break in future versions
of aiosqlite.
"""
if self._connection is not None:
self._connection._tx.queue.clear()
self._connection._running = False
self._connection = None

@asynccontextmanager
async def bulk_commit(self):
"""Contextmanager to more efficiently write a large number of records at once
Expand Down
20 changes: 18 additions & 2 deletions test/integration/base_backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ class BaseBackendTest:

@asynccontextmanager
async def init_session(self, clear=True, **kwargs) -> AsyncIterator[CachedSession]:
session = await self._init_session(clear=clear, **kwargs)
async with session:
yield session

async def _init_session(self, clear=True, **kwargs) -> CachedSession:
kwargs.setdefault('allowed_methods', ALL_METHODS)
cache = self.backend_class(CACHE_NAME, **self.init_kwargs, **kwargs)
if clear:
await cache.clear()

async with CachedSession(cache=cache, **self.init_kwargs, **kwargs) as session:
yield session
return CachedSession(cache=cache, **self.init_kwargs, **kwargs)

@pytest.mark.parametrize('method', HTTPBIN_METHODS)
@pytest.mark.parametrize('field', ['params', 'data', 'json'])
Expand Down Expand Up @@ -100,6 +104,18 @@ async def get_url(mysession, url):
responses = await asyncio.gather(*tasks)
assert all([r.from_cache is True for r in responses])

async def test_without_contextmanager(self):
"""Test that the cache backend can be safely used without the CachedSession contextmanager.
An "unclosed ClientSession" warning is expected here, however.
"""
session = await self._init_session()
await session.get(httpbin('get'))
del session

session = await self._init_session(clear=False)
r = await session.get(httpbin('get'))
assert r.from_cache is True

async def test_request__expire_after(self):
async with self.init_session() as session:
await session.get(httpbin('get'), expire_after=1)
Expand Down
8 changes: 8 additions & 0 deletions test/integration/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ async def test_content_reset(self):
content_2 = await cached_response_2.read()
assert content_1 == content_2 == original_content

async def test_without_contextmanager(self):
"""Test that the cache backend can be safely used without the CachedSession contextmanager.
An "unclosed ClientSession" warning is expected here, however.
"""
session = await self._init_session()
await session.get(httpbin('get'))
del session

# Serialization tests don't apply to in-memory cache
async def test_serializer__pickle(self):
pass
Expand Down

0 comments on commit 8ef7b80

Please sign in to comment.