mirror of
https://github.com/simonw/datasette.git
synced 2026-05-27 20:36:17 +02:00
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>
56 lines
1.5 KiB
Python
56 lines
1.5 KiB
Python
"""
|
|
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}"
|