-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Close SQLite connection if session is deleted and thread is still running #189
Conversation
b96d6f8
to
debe465
Compare
Codecov ReportAll modified lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #189 +/- ##
==========================================
+ Coverage 97.22% 97.23% +0.01%
==========================================
Files 10 10
Lines 937 942 +5
Branches 172 173 +1
==========================================
+ Hits 911 916 +5
Misses 15 15
Partials 11 11
☔ View full report in Codecov by Sentry. |
…thout a contextmanager
0003bfd
to
f479716
Compare
f479716
to
0e535c5
Compare
Hey @JWCook! I think this solves most of #187. The thread can still hang if the process receives a SigInt (ctrl + c), but I am not sure how much can be done to avoid that. I pulled the branch and ran the sqlite integration tests locally and pytest showing a pytest test/integration/test_sqlite.py
test/integration/test_sqlite.py::TestSQLiteCache::test_concurrent_bulk_commit
/home/test/aiohttp-client-cache/sqlite-nocontext-autoclose/aiohttp_client_cache/backends/sqlite.py:116: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
self._connection._tx.queue.clear()
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html At least on my machine, wrapping @@ -53,17 +53,24 @@ class TestSQLiteCache(BaseStorageTest):
mock_connection = AsyncMock()
mock_sqlite.connect = AsyncMock(return_value=mock_connection)
- async with self.init_cache() as cache:
+ from contextlib import asynccontextmanager
+
+ @asynccontextmanager
+ async def bulk_commit_ctx():
+ async with self.init_cache() as cache:
+
+ async def bulk_commit_items(n_items):
+ async with cache.bulk_commit():
+ for i in range(n_items):
+ await cache.write(f'key_{n_items}_{i}', f'value_{i}')
+ yield bulk_commit_items
- async def bulk_commit_items(n_items):
- async with cache.bulk_commit():
- for i in range(n_items):
- await cache.write(f'key_{n_items}_{i}', f'value_{i}')
+ async with bulk_commit_ctx() as bulk_commit_items:
- assert mock_connection.commit.call_count == 1
- tasks = [asyncio.create_task(bulk_commit_items(n)) for n in [10, 100, 1000, 10000]]
- await asyncio.gather(*tasks)
- assert mock_connection.commit.call_count == 5
+ assert mock_connection.commit.call_count == 1
+ tasks = [asyncio.create_task(bulk_commit_items(n)) for n in [10, 100, 1000, 10000]]
+ await asyncio.gather(*tasks)
+ assert mock_connection.commit.call_count == 5 While resolving the above warning, I ended up running |
I should have read more closely when reviewing the tests, I now see the comment in the 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. |
68a6612
to
4edd51b
Compare
…test_concurrent_bulk_commit()
4edd51b
to
52f3442
Compare
I was thinking about that as well. Options would include handling
I wasn't able to reproduce this locally, but this makes sense, so I applied your patch. Thanks for the suggestion! |
Here is one possible solution for #187. This works based on the logic of
aiosqlite.Connection.run()
(implementation ofThread.run()
). It can be forced to exit without blocking or scheduling any tasks by setting an internal flag and emptying its task queue. The underlyingsqlite3.Connection
object will not be closed (which is a blocking operation), but in my experience this doesn't cause any problems.This will only be used in the case where the
SQLiteCache
object is being garbage-collected and the connection has not yet been closed implicitly (via contextmanager exit) or explicitly (viaclose()
).This does have the potential for data loss, if somehow the session or cache objects are deleted while async tasks have been created but not awaited. This seems unlikely to happen in practice, and I think it's a reasonable tradeoff, but it does leave some room for possible future improvement.