Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/everos/memory/cascade/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,30 @@ async def _enqueue_async(
def _relative_to_root(root: Path, raw: str) -> str | None:
"""Return ``raw`` relative to ``root`` using POSIX separators.

``None`` when the path is outside the memory root (defensive — the
watcher only watches inside ``root``, but external symlinks could
surface).
watchdog reports absolute paths inside the watched root (``root`` is
already resolved by ``MemoryRoot``), so the common case is a pure string
operation and matches the scanner's key byte for byte. ``resolve()`` on
Windows is two ``GetFinalPathNameByHandle`` opens plus a ``stat()`` per
event, and a per-component walk when the path is already gone — about
40 % of the worker threads' CPU under write load (py-spy on the 2 h
soak). It is kept for the defensive case only: a textual path that is
not under ``root`` (an external symlink surfacing inside the tree) or
one carrying ``..`` segments. Known residue: if ``ReadDirectoryChangesW``
reports a file by its 8.3 short name, the key differs from the
scanner's long-name key until the next sweep reconciles it.

``None`` when the path is outside the memory root even after resolving.
"""
path = Path(raw)
if ".." not in path.parts:
try:
return path.relative_to(root).as_posix()
except ValueError:
pass
try:
rel = Path(raw).resolve().relative_to(root)
return path.resolve().relative_to(root).as_posix()
except ValueError:
return None
return rel.as_posix()


def _safe_mtime(raw: str) -> float:
Expand Down
43 changes: 43 additions & 0 deletions tests/unit/test_memory/test_cascade/test_watcher_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

from __future__ import annotations

import sys
from pathlib import Path

import pytest

from everos.memory.cascade.watcher import _relative_to_root, _safe_mtime


Expand All @@ -34,3 +37,43 @@ def test_safe_mtime_existing_path_returns_positive(tmp_path: Path) -> None:
f = tmp_path / "f.md"
f.write_text("ok")
assert _safe_mtime(str(f)) > 0


def test_relative_to_root_within_does_not_touch_the_filesystem(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""An in-root path is relativised textually — no ``resolve()``.

``resolve()`` is a filesystem round trip per component (through
Defender on Windows) and the watcher calls this on every event.
"""

def _boom(self: Path, strict: bool = False) -> Path:
raise AssertionError("resolve() must not run for an in-root path")

monkeypatch.setattr(Path, "resolve", _boom)
target = tmp_path / "users" / "u1" / "x.md" # need not exist
assert _relative_to_root(tmp_path, str(target)) == "users/u1/x.md"


@pytest.mark.skipif(sys.platform == "win32", reason="symlinks need a privilege")
def test_relative_to_root_via_symlink_still_resolves(
tmp_path: Path, tmp_path_factory: pytest.TempPathFactory
) -> None:
"""A path that reaches the root through an external symlink still maps."""
root = tmp_path.resolve()
(root / "users" / "u1").mkdir(parents=True)
(root / "users" / "u1" / "x.md").write_text("x", encoding="utf-8")
link = tmp_path_factory.mktemp("elsewhere") / "link_to_root"
link.symlink_to(root, target_is_directory=True)
assert (
_relative_to_root(root, str(link / "users" / "u1" / "x.md")) == "users/u1/x.md"
)


def test_relative_to_root_normalises_dotdot_segments(tmp_path: Path) -> None:
"""A ``..`` segment takes the resolving path so the key stays canonical."""
root = tmp_path.resolve()
(root / "users" / "u1").mkdir(parents=True)
raw = str(root / "users" / "u2" / ".." / "u1" / "x.md")
assert _relative_to_root(root, raw) == "users/u1/x.md"
Loading