mirror of
https://github.com/usestrix/strix.git
synced 2026-08-25 12:22:37 +02:00
A scan runs dozens of agents concurrently, each holding a sandbox session, a browser session, a model client, and a SQLite handle. At the common 1024 soft file-descriptor limit that budget is exhausted around ~32 agents, after which SQLite can no longer open agents.db and agents fail en masse with "unable to open database file" (an fd-exhaustion symptom, not DB corruption). Measured directly: fd usage scales ~linearly with concurrent agents and hits 1024 at 32 agents; fds are reclaimed as agents finish, so this is a ceiling problem, not a leak. Strix never set its own limit, inheriting whatever the launching shell had (often 1024). Raise RLIMIT_NOFILE toward the hard cap at scan start (best-effort, idempotent, POSIX-guarded; warns when the hard cap is itself too low to lift without a privileged operator). Runs no longer depend on the operator setting ulimit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
"""The scan runner raises the open-file soft limit so many-agent scans don't
|
|
exhaust file descriptors (surfacing as SQLite 'unable to open database file')."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from strix.core.runner import raise_open_file_limit
|
|
|
|
|
|
resource = pytest.importorskip("resource")
|
|
|
|
|
|
@pytest.fixture
|
|
def _restore_nofile() -> None:
|
|
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
try:
|
|
yield
|
|
finally:
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
|
|
|
|
|
|
@pytest.mark.usefixtures("_restore_nofile")
|
|
def test_raises_soft_limit_toward_hard() -> None:
|
|
_, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
if hard != resource.RLIM_INFINITY and hard <= 1024:
|
|
pytest.skip("hard limit too low to raise in this environment")
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (1024, hard))
|
|
|
|
raise_open_file_limit(4096)
|
|
|
|
soft, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
assert soft >= min(4096, hard)
|
|
|
|
|
|
@pytest.mark.usefixtures("_restore_nofile")
|
|
def test_never_lowers_an_already_high_limit() -> None:
|
|
_, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
if hard == resource.RLIM_INFINITY or hard < 8192:
|
|
pytest.skip("need headroom above the requested minimum")
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (8192, hard))
|
|
|
|
raise_open_file_limit(4096)
|
|
|
|
soft, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
assert soft == 8192
|
|
|
|
|
|
@pytest.mark.usefixtures("_restore_nofile")
|
|
def test_does_not_exceed_the_hard_cap() -> None:
|
|
_, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
if hard == resource.RLIM_INFINITY:
|
|
pytest.skip("no finite hard cap to test against")
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (min(1024, hard), hard))
|
|
|
|
raise_open_file_limit(hard + 1_000_000) # ask for more than allowed
|
|
|
|
soft, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
assert soft <= hard
|