summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_disk_io_off_loop.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_disk_io_off_loop.py')
-rw-r--r--packages/meshbay-node/tests/test_disk_io_off_loop.py84
1 files changed, 60 insertions, 24 deletions
diff --git a/packages/meshbay-node/tests/test_disk_io_off_loop.py b/packages/meshbay-node/tests/test_disk_io_off_loop.py
index 2811836..2179e66 100644
--- a/packages/meshbay-node/tests/test_disk_io_off_loop.py
+++ b/packages/meshbay-node/tests/test_disk_io_off_loop.py
@@ -19,8 +19,8 @@ means two reads of the same file can never be inside it at once, and that is
what makes the single `f.seek()`/`f.read()` pair safe without a lock.
"""
+import ast
import asyncio
-import re
import threading
import time
from pathlib import Path
@@ -204,29 +204,6 @@ async def test_one_root_set_reads_one_chunk_at_a_time(tmp_path):
assert peak == 1, f"{peak} reads of one root set were inside the disk at once"
-def test_no_handler_resolves_a_path_on_the_loop():
- """
- `entry_abs_path` is `Path.resolve()`, which is syscalls — it belongs on the
- disk thread with everything else.
-
- This reads the source because there is nothing else to read: a handler added
- later that resolves an entry inline would pass every test above, since those
- only exercise the handlers that exist today. `_locate` is the one place
- allowed to call it, and `off_disk` is how `_locate` is reached.
- """
- src = Path(webrtc_server.__file__).read_text()
- # Every call site, not the first one: a guard that stops at the first
- # occurrence stops guarding the moment a new call is inserted above it.
- calls = [m.start() for m in re.finditer(r"\bentry_abs_path\(", src)]
- body = re.search(r"\ndef _locate\(.*?\n(?=\n\ndef |\n\nclass )", src, re.S)
- assert body, "_locate is gone or has been renamed — this guard needs rewriting"
- allowed = range(body.start(), body.end())
- stray = [c for c in calls if c not in allowed]
- assert not stray, (
- f"{len(stray)} call(s) to entry_abs_path outside _locate: "
- f"resolve a path through `off_disk(roots, _locate, ...)` instead")
-
-
async def test_the_availability_poll_does_not_stop_the_loop(tmp_path, monkeypatch):
"""
The poll is the one that runs whether anybody asked for anything.
@@ -255,6 +232,65 @@ async def test_the_availability_poll_does_not_stop_the_loop(tmp_path, monkeypatc
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s poll")
+def test_no_handler_touches_the_disk_on_the_loop():
+ """
+ The whole class, not the calls that were fixed.
+
+ Every measured test above exercises a handler that exists today; a new one
+ that stats a root inline would pass all of them. So this walks the module's
+ syntax tree instead and fails on any filesystem call outside the few
+ functions written to be run through `off_disk`.
+
+ `entry_abs_path` and `safe_subdir` are in the list because both are
+ `Path.resolve()` underneath, and a resolve is syscalls whatever it is
+ called.
+ """
+ blocking = {"is_dir", "exists", "mkdir", "unlink", "rename", "rmdir",
+ "iterdir", "read_bytes", "write_bytes", "stat",
+ "resolve", "entry_abs_path"}
+ # Written to block, and reached only through `off_disk`.
+ on_the_disk_thread = {"_locate", "_append_chunk", "_read_and_encrypt",
+ "_mkdir_if_absent", "_is_empty_dir", "_rmdir_if_empty",
+ "safe_subdir"}
+ # ffmpeg's own output, under `tempfile.mkstemp` on the system disk — not a
+ # group root, so not what spins down. Listed rather than silently allowed:
+ # these still read a whole transcode into memory from the loop, and the day
+ # that matters it is a different measurement from this one.
+ ffmpeg_scratch = {"_transcode_audio_to_aac", "_seek_lands_at",
+ "_extract_subtitle_to_webvtt"}
+ allowed = on_the_disk_thread | ffmpeg_scratch
+
+ found = []
+
+ def visit(node, owner):
+ for child in ast.iter_child_nodes(node):
+ if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ visit(child, child.name)
+ continue
+ if isinstance(child, ast.Call):
+ fn = child.func
+ name = (fn.attr if isinstance(fn, ast.Attribute)
+ else getattr(fn, "id", ""))
+ if name in blocking and owner not in allowed:
+ found.append(f"{owner} calls {name}() at line {child.lineno}")
+ visit(child, owner)
+
+ tree = ast.parse(Path(webrtc_server.__file__).read_text())
+ for node in tree.body:
+ if isinstance(node, ast.ClassDef):
+ for member in node.body:
+ if isinstance(member, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ visit(member, member.name)
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ visit(node, node.name)
+
+ assert not found, (
+ "filesystem calls made from the event loop:\n "
+ + "\n ".join(found)
+ + "\nRun them through `off_disk(roots, ...)`, or put the call in a "
+ "helper that is only reached that way.")
+
+
def _upload_session(tmp_path):
"""One connection into a group with one writable root, as a node has."""
shared = tmp_path / "shared"