Spaces:
Running
Running
Download tests/test_event_loop_blocking.py from siddhm11/ResearchIT: direct link, hf CLI and curl.
- Browser
- Download file 4.01 kB
-
https://huggingface.co/spaces/siddhm11/ResearchIT/resolve/main/tests/test_event_loop_blocking.py
- Command line
-
hf download hf://spaces/siddhm11/ResearchIT/tests/test_event_loop_blocking.py
-
curl -L -o test_event_loop_blocking.py https://huggingface.co/spaces/siddhm11/ResearchIT/resolve/main/tests/test_event_loop_blocking.py
4.01 kB
| """ | |
| Nothing may block the event loop with sidecar I/O. | |
| `app/local_meta.py` is synchronous sqlite3 over a 2.7 GB file. A blocking call | |
| inside an `async def` stalls EVERY in-flight request, including ones that never | |
| touch the sidecar — and this runs as a single uvicorn worker on 2 vCPUs, so | |
| there is no other worker to absorb it. | |
| Measured on a 300k-row stand-in: a 72 ms query held the loop for 78 ms; the | |
| same query via a thread held it for 7 ms. The real sidecar has 1.8M rows. | |
| The shared connection is opened read-only with check_same_thread=False | |
| precisely so it can be used from a thread (local_meta._probe). | |
| """ | |
| import ast | |
| import asyncio | |
| import pathlib | |
| import sqlite3 | |
| import tempfile | |
| import time | |
| import pytest | |
| # Functions that touch the sidecar's connection and therefore must not be | |
| # called directly from a coroutine. | |
| BLOCKING = {"fetch_rows", "fetch_trending", "pub_year_month", "newest_update_date"} | |
| # is_available() and stats() are cheap after the first probe and are excluded. | |
| def _async_callers_of_blocking_sidecar_fns(): | |
| """Every `local_meta.<blocking>()` call lexically inside an async def.""" | |
| offenders = [] | |
| for path in sorted(pathlib.Path("app").rglob("*.py")): | |
| if path.name == "local_meta.py": | |
| continue | |
| tree = ast.parse(path.read_text(), filename=str(path)) | |
| for fn in ast.walk(tree): | |
| if not isinstance(fn, ast.AsyncFunctionDef): | |
| continue | |
| for node in ast.walk(fn): | |
| if not isinstance(node, ast.Call): | |
| continue | |
| f = node.func | |
| if (isinstance(f, ast.Attribute) and f.attr in BLOCKING | |
| and isinstance(f.value, ast.Name) | |
| and f.value.id == "local_meta"): | |
| offenders.append(f"{path}:{node.lineno} in {fn.name}()") | |
| return offenders | |
| def test_no_coroutine_calls_the_sidecar_directly(): | |
| offenders = _async_callers_of_blocking_sidecar_fns() | |
| assert not offenders, ( | |
| "sidecar I/O called directly from a coroutine — wrap in " | |
| "asyncio.to_thread: " + ", ".join(offenders)) | |
| def test_the_check_would_catch_a_direct_call(): | |
| """Guard against the guard silently matching nothing.""" | |
| src = ( | |
| "import asyncio\n" | |
| "from app import local_meta\n" | |
| "async def handler(ids):\n" | |
| " return local_meta.fetch_rows(ids)\n" | |
| ) | |
| tree = ast.parse(src) | |
| hits = [ | |
| n.lineno for fn in ast.walk(tree) if isinstance(fn, ast.AsyncFunctionDef) | |
| for n in ast.walk(fn) | |
| if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute) | |
| and n.func.attr in BLOCKING | |
| ] | |
| assert hits == [4] | |
| async def test_a_threaded_query_does_not_stall_the_loop(): | |
| """The property itself, not just the call shape.""" | |
| with tempfile.TemporaryDirectory() as d: | |
| path = f"{d}/t.db" | |
| c = sqlite3.connect(path) | |
| c.execute("CREATE TABLE papers (arxiv_id TEXT, blob TEXT)") | |
| c.executemany("INSERT INTO papers VALUES (?,?)", | |
| [(f"p{i}", "x" * 200) for i in range(120_000)]) | |
| c.commit() | |
| c.close() | |
| conn = sqlite3.connect(f"file:{path}?mode=ro", uri=True, | |
| check_same_thread=False) | |
| def slow(): | |
| return conn.execute( | |
| "SELECT COUNT(*) FROM papers WHERE blob LIKE '%zz%'").fetchone() | |
| gaps = [] | |
| stop = asyncio.Event() | |
| async def heartbeat(): | |
| last = time.perf_counter() | |
| while not stop.is_set(): | |
| await asyncio.sleep(0.005) | |
| now = time.perf_counter() | |
| gaps.append(now - last) | |
| last = now | |
| hb = asyncio.create_task(heartbeat()) | |
| await asyncio.sleep(0.03) | |
| await asyncio.to_thread(slow) | |
| stop.set() | |
| await hb | |
| conn.close() | |
| assert max(gaps) < 0.05, ( | |
| f"loop stalled {max(gaps)*1000:.0f} ms even through a thread") | |