mirror of
https://github.com/simonw/datasette.git
synced 2026-05-31 06:07:05 +02:00
FD-leak regression test for Datasette.close()
Creates and disposes 50 Datasette instances in a loop and asserts that the number of open file descriptors and live threads does not grow, exercising the full close() path end to end. Refs #2692 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
34cc320eab
commit
c0153386ef
2 changed files with 57 additions and 0 deletions
|
|
@ -80,6 +80,7 @@ dev = [
|
|||
"myst-parser",
|
||||
"sphinx-markdown-builder",
|
||||
"ruamel.yaml",
|
||||
"psutil>=5.9",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
|||
56
tests/test_fd_leak.py
Normal file
56
tests/test_fd_leak.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Regression test for https://github.com/simonw/datasette/issues/2692 —
|
||||
confirm that creating and closing Datasette instances in a loop does not
|
||||
leak open file descriptors.
|
||||
|
||||
Each Datasette() with is_temp_disk internal DB opens a temp file and a
|
||||
write thread with its own SQLite connection. Without Datasette.close()
|
||||
nothing unwinds this state, and a large pytest run exhausts the process
|
||||
FD limit.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError: # pragma: no cover
|
||||
psutil = None
|
||||
|
||||
from datasette.app import Datasette
|
||||
|
||||
|
||||
def _count_open_files():
|
||||
return len(psutil.Process().open_files())
|
||||
|
||||
|
||||
def _count_threads():
|
||||
return threading.active_count()
|
||||
|
||||
|
||||
@pytest.mark.skipif(psutil is None, reason="psutil not installed")
|
||||
def test_close_releases_file_descriptors():
|
||||
# Warm-up so Python/library caches don't skew the baseline
|
||||
ds = Datasette(memory=True)
|
||||
asyncio.run(ds.invoke_startup())
|
||||
ds.close()
|
||||
|
||||
baseline_fds = _count_open_files()
|
||||
baseline_threads = _count_threads()
|
||||
|
||||
for _ in range(50):
|
||||
ds = Datasette(memory=True)
|
||||
asyncio.run(ds.invoke_startup())
|
||||
ds.close()
|
||||
|
||||
after_fds = _count_open_files()
|
||||
after_threads = _count_threads()
|
||||
|
||||
assert (
|
||||
after_fds - baseline_fds <= 2
|
||||
), f"Leaked FDs: baseline={baseline_fds}, after=50 iterations={after_fds}"
|
||||
assert (
|
||||
after_threads - baseline_threads <= 2
|
||||
), f"Leaked threads: baseline={baseline_threads}, after={after_threads}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue