You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ezmsg clients resolve the graph address from EZMSG_GRAPHSERVER_ADDR, defaulting to 127.0.0.1:25978, and GraphService.ensure() attaches to whatever is already listening there. They auto-start a server only when nothing is listening and the above env var is unset:
# graphserver.py - GraphService.ensure()elifself._addressisNone:
# Only auto-start if env var not forcing a locationensure_server=self.ADDR_ENVnotinos.environ
That attach-first behavior is right for production (one long-lived server shared by many processes), but it's exactly wrong in my opinion for the test suite, which inherits it: every test that reaches ez.run(...) or GraphContext() without an explicit address silently joins whatever server happens to be running on the developer's machine.
The suite already carries evidence of this. test_attach is skipped all the time: tests/test_attach.py::test_attach is @pytest.mark.skip(reason="canonical port isn't always available").
Observed symptoms
I can imagine that in many set ups, a user will not necessarily run into issues. But as an example, my set up does regularly. I regularly work on Windows and Windows Subsystem for Linux (WSL2) on the same machine. If I have an ezmsg application server running under WSL2 on the default port, then this server is reachable from Windows via localhost forwarding. A full pytest tests/ run on the Windows side produces:
18–35 failures across test_run.py, test_clean_shutdown.py, tests/messages/test_key.py, test_profiler.py, with FileNotFoundError: [WinError 2] ... 'psm_xxxxxxxx'. The tests attach shared-memory segments that exist only in the foreign server's process/OS namespace (see the companion SHM-boundary issue), plus PermissionError variants.
stale-graph interference: the foreign server carries live topology the tests never created.
worst of all, a shutdown hang: pytest printed its summary after about 2 minutes and the interpreter then sat for 90+ minutes on dangling cross-boundary connections, so CI-style timeouts fire long after and far away from the cause.
The failure set varies with what the foreign server happens to be doing, so the suite looks flaky rather than broken.
Steps to reproduce
In one terminal, keep any ezmsg app's server alive on the default port. ezmsg serve --address 127.0.0.1:25978 is enough. (For the full cross-OS effect, run it inside WSL2 and run the tests from Windows; localhost forwarding makes the server reachable while its SHM namespace is not.)
In another terminal: pytest tests/
Observe SHM attach failures in graph-running tests, and (depending on timing) a post-summary interpreter hang.
Solution criteria
Tests never connect to a server the suite did not start, regardless of environment (developer machine with a live server, CI, WSL).
Per-test graph isolation is preserved: each test still sees an empty graph.
Production attach semantics are untouched - this is a test-suite fix, not a runtime behaviour change.
Tests that manage servers/addresses explicitly (GraphService(address=...), GraphContext(addr, auto_start=...), monkeypatched env) keep working unchanged.
The isolation must propagate to subprocesses the tests spawn. Backend processes and the example scripts test_clean_shutdown runs, which means it must ride the environment, not just in-process state.
The attach code path stays exercised (ideally test_attach becomes un-skippable), and the auto-start path keeps deliberate coverage.
Possible solutions
Hermetic conftest (my preferred, have PR on the way). A root tests/conftest.py that
(a) pins EZMSG_GRAPHSERVER_ADDR to a session-private free port before anything imports ezmsg. This is required because netprotocol.GRAPHSERVER_ADDR (and channelmanager's copy) bake the address in at import time, and because children inherit the env
(b) starts a fresh GraphServer at that address for every test via an autouse fixture. Both halves are required: pinning the env alone disables implicit auto-start (see ensure() above), stranding every ez.run test on connection-refused.
Results on the conflicting machine set up described above: the same suite goes from 35 failures + shutdown hang to all-green with a clean exit, and test_attach runs un-skipped against the per-test server (spawned processes attach through the inherited env), restoring real multi-process attach coverage.
Fail-fast guard (weaker fallback). A conftest check that refuses to run the suite when something is already listening on the resolved default address, with a message naming the culprit. Doesn't make the suite runnable on such machines, but converts an hour of misleading flakiness into an immediate, accurate error. Could be combined with (1) as an assertion that the pinned port is free.
Other variants considered and discarded and why:
One session-scoped server instead of per-test: cheaper, but graph state (topology, settings, client registrations) leaks between tests. The same fresh-graph assumption violation this issue is about, just self-inflicted.
Per-test ephemeral ports with patched constants, no env var: breaks subprocess propagation: children re-import ezmsg and resolve the real default; only the environment reaches them.
What happens today
ezmsg clients resolve the graph address from
EZMSG_GRAPHSERVER_ADDR, defaulting to127.0.0.1:25978, andGraphService.ensure()attaches to whatever is already listening there. They auto-start a server only when nothing is listening and the above env var is unset:That attach-first behavior is right for production (one long-lived server shared by many processes), but it's exactly wrong in my opinion for the test suite, which inherits it: every test that reaches
ez.run(...)orGraphContext()without an explicit address silently joins whatever server happens to be running on the developer's machine.The suite already carries evidence of this.
test_attachis skipped all the time:tests/test_attach.py::test_attachis@pytest.mark.skip(reason="canonical port isn't always available").Observed symptoms
I can imagine that in many set ups, a user will not necessarily run into issues. But as an example, my set up does regularly. I regularly work on Windows and Windows Subsystem for Linux (WSL2) on the same machine. If I have an ezmsg application server running under WSL2 on the default port, then this server is reachable from Windows via localhost forwarding. A full
pytest tests/run on the Windows side produces:test_run.py,test_clean_shutdown.py,tests/messages/test_key.py,test_profiler.py, withFileNotFoundError: [WinError 2] ... 'psm_xxxxxxxx'. The tests attach shared-memory segments that exist only in the foreign server's process/OS namespace (see the companion SHM-boundary issue), plusPermissionErrorvariants.The failure set varies with what the foreign server happens to be doing, so the suite looks flaky rather than broken.
Steps to reproduce
ezmsg serve --address 127.0.0.1:25978is enough. (For the full cross-OS effect, run it inside WSL2 and run the tests from Windows; localhost forwarding makes the server reachable while its SHM namespace is not.)pytest tests/Solution criteria
GraphService(address=...),GraphContext(addr, auto_start=...), monkeypatched env) keep working unchanged.test_clean_shutdownruns, which means it must ride the environment, not just in-process state.test_attachbecomes un-skippable), and the auto-start path keeps deliberate coverage.Possible solutions
Hermetic conftest (my preferred, have PR on the way). A root
tests/conftest.pythat(a) pins
EZMSG_GRAPHSERVER_ADDRto a session-private free port before anything imports ezmsg. This is required becausenetprotocol.GRAPHSERVER_ADDR(andchannelmanager's copy) bake the address in at import time, and because children inherit the env(b) starts a fresh
GraphServerat that address for every test via an autouse fixture. Both halves are required: pinning the env alone disables implicit auto-start (seeensure()above), stranding everyez.runtest on connection-refused.Results on the conflicting machine set up described above: the same suite goes from 35 failures + shutdown hang to all-green with a clean exit, and
test_attachruns un-skipped against the per-test server (spawned processes attach through the inherited env), restoring real multi-process attach coverage.Fail-fast guard (weaker fallback). A conftest check that refuses to run the suite when something is already listening on the resolved default address, with a message naming the culprit. Doesn't make the suite runnable on such machines, but converts an hour of misleading flakiness into an immediate, accurate error. Could be combined with (1) as an assertion that the pinned port is free.
Other variants considered and discarded and why: