deer-flow/backend/tests/blocking_io/test_managed_subagent_file_probe.py
paer 2dc68b242f
fix(subagents): prevent concurrent file-backed creates from overwriting definitions (#8132)
* docs(subagents): describe create-only file publication

* fix(subagents): prevent concurrent create overwrites

* test(subagents): cover independent create races

* docs(persistence): record create-only publication

* docs(config): explain managed subagent file claims

* docs(persistence): retain create-only publication rule

* docs(subagents): retain shared file create semantics

* fix(gateway): probe managed subagent hard links on startup

* test(gateway): cover managed subagent volume probes

* test(gateway): keep volume probes off the event loop

* docs(config): describe startup hard-link warnings

* docs(changelog): reference managed subagent fix #8132

* docs(changelog): keep the fix entry clear of append conflicts
2026-11-12 14:39:25 +08:00

60 lines
2.1 KiB
Python

"""The startup volume probe runs once, off the Gateway event loop."""
from __future__ import annotations
import threading
import pytest
from fastapi import FastAPI
from app.gateway import deps
from deerflow.config.app_config import AppConfig
from deerflow.config.paths import Paths
class _ReachedNextStartupCheck(Exception):
pass
@pytest.fixture
def volume_paths(tmp_path, monkeypatch):
paths = Paths(base_dir=tmp_path)
monkeypatch.setattr("deerflow.config.paths.get_paths", lambda: paths)
return paths
@pytest.mark.asyncio
@pytest.mark.parametrize("backend", [None, "file", "db"], ids=["default-file", "explicit-file", "database"])
async def test_lifespan_probes_only_file_storage_off_loop(volume_paths, monkeypatch, backend):
for name in ("GATEWAY_WORKERS", "WEB_CONCURRENCY", "DEER_FLOW_MULTI_INSTANCE"):
monkeypatch.delenv(name, raising=False)
paths = volume_paths
config = AppConfig(sandbox={"use": "deerflow.sandbox.local:LocalSandboxProvider"}, database={"backend": "sqlite"})
if backend is not None:
config.agent_storage.backend = backend
loop_thread = threading.get_ident()
probe_threads = []
original_link = deps.os.link
def observed_link(source, destination):
probe_threads.append(threading.get_ident())
original_link(source, destination)
def reached_next_check(_config):
raise _ReachedNextStartupCheck
monkeypatch.setattr(deps.os, "link", observed_link)
# Stop after the real startup probe, before opening any runtime resources.
monkeypatch.setattr(deps, "_validate_login_throttle_storage", reached_next_check)
with pytest.raises(_ReachedNextStartupCheck):
async with deps.langgraph_runtime(FastAPI(), config):
pytest.fail("the following startup check must abort this focused test")
if backend == "db":
assert probe_threads == []
assert not paths.managed_subagents_dir.exists()
else:
assert len(probe_threads) == 1
assert probe_threads[0] != loop_thread
assert list(paths.managed_subagents_dir.iterdir()) == []