aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-13 00:30:39 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-13 00:30:39 +0200
commitefbcff05721e71f1fdfcc30b7ee1b44ca1670762 (patch)
tree23018295b653510900ac24b71a780d74e32a01a2 /packages/meshbay-node
parente9a93c21ff28daa6f4017849b42fe073a5ee2005 (diff)
downloadmeshbay-efbcff05721e71f1fdfcc30b7ee1b44ca1670762.tar.gz
test(node): run the connection processes on Windows, source and frozen
Connection processes and the larger UDP receive buffer were written and measured on Linux; nothing had run on Windows, and `meshbay-node.exe webrtc-worker` had never run from a frozen build on any system. - test_connection_processes.py failed on Windows (8 tests): the selector loop the suite uses there cannot start a process. A test marked `daemon_loop` now runs on the daemon's own loop, chosen through pytest-asyncio's loop factories. - The build starts both frozen executables as connection processes and carries a connection through each (smoke-connection-process.py). The daemon's own smoke test never signs in to a hub, which is when the processes start. - The warning for a receive buffer below what was asked named net.core.rmem_max on every system; it names it on Linux only. Windows grants the 4 MB. Measured on an installed build in service mode, docs/WINDOWS-PORT.md §5.12: two processes at startup and on a live change, a browser's handshake, chat, a 200 MB download over 8 connections across both processes, an upload, a video stream, and stop, restart and start leaving no process behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py11
-rw-r--r--packages/meshbay-node/tests/conftest.py31
-rw-r--r--packages/meshbay-node/tests/test_connection_processes.py7
-rw-r--r--packages/meshbay-node/tests/test_packaging_win.py24
4 files changed, 70 insertions, 3 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index 1976ad4..fea642a 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -24,6 +24,7 @@ Signaling flow (handled externally by the hub):
import asyncio
import logging
+import sys
import time
from typing import Any
@@ -157,11 +158,15 @@ class WebRTCTransport:
if got < udp_buffer.WANTED:
# Said once, with what to change: nothing else would tell an
# operator why uploads to this node stay around 6 MB/s.
+ # The remedy is Linux's: Windows grants what is asked (measured,
+ # 4 MB and beyond), and naming a sysctl there would send its
+ # operator looking for a setting the system does not have.
+ remedy = (f"; raise net.core.rmem_max to {udp_buffer.WANTED} to lift it"
+ if sys.platform.startswith("linux") else "")
log.warning(
"UDP receive buffer is %d KB per socket, below the %d KB an "
- "upload needs to run at full speed; raise net.core.rmem_max "
- "to %d to lift it", got // 1024, udp_buffer.WANTED // 1024,
- udp_buffer.WANTED)
+ "upload needs to run at full speed%s",
+ got // 1024, udp_buffer.WANTED // 1024, remedy)
else:
log.info("UDP receive buffer: %d KB per socket", got // 1024)
self._workers = WorkerPool(
diff --git a/packages/meshbay-node/tests/conftest.py b/packages/meshbay-node/tests/conftest.py
index 32c9b3d..545528b 100644
--- a/packages/meshbay-node/tests/conftest.py
+++ b/packages/meshbay-node/tests/conftest.py
@@ -1,9 +1,11 @@
"""Shared fixtures and helpers for node tests."""
+import asyncio
import sys
from pathlib import Path
import pytest
+import pytest_asyncio
from meshbay_node.roots import RootSet
# ffmpeg / ffprobe run via asyncio.create_subprocess_exec, which needs the
@@ -17,6 +19,35 @@ needs_subprocess = pytest.mark.skipif(
"SelectorEventLoop for aiortc",
)
+# The other way round, for a test that starts processes and has no two aiortc
+# ends in one process: on Windows it runs on the loop the daemon itself runs
+# on. The loop is chosen per test through pytest-asyncio's loop factories;
+# a version without them leaves the selector loop in place, where such a test
+# cannot run.
+_LOOP_FACTORIES = hasattr(
+ getattr(pytest_asyncio.plugin, "PytestAsyncioSpecs", None), "pytest_asyncio_loop_factories")
+on_the_daemons_loop = [
+ pytest.mark.daemon_loop,
+ pytest.mark.skipif(
+ sys.platform == "win32" and not _LOOP_FACTORIES,
+ reason="starts processes, which needs the ProactorEventLoop: asked for through "
+ "pytest-asyncio's loop factories, which this version does not have"),
+]
+
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers", "daemon_loop: on Windows, run on the event loop the daemon runs on")
+
+
+@pytest.hookimpl(optionalhook=True)
+def pytest_asyncio_loop_factories(config, item):
+ """The event loop of each async test here: the one it always had, except
+ for a test marked `daemon_loop` on Windows."""
+ if sys.platform == "win32" and item.get_closest_marker("daemon_loop"):
+ return {"proactor": asyncio.ProactorEventLoop}
+ return {"default": asyncio.new_event_loop}
+
@pytest.fixture(autouse=True)
def _no_log_file_in_the_developers_profile(monkeypatch):
"""A test that runs the daemon's entry point must not open its log file.
diff --git a/packages/meshbay-node/tests/test_connection_processes.py b/packages/meshbay-node/tests/test_connection_processes.py
index 47aded0..ab9dfd3 100644
--- a/packages/meshbay-node/tests/test_connection_processes.py
+++ b/packages/meshbay-node/tests/test_connection_processes.py
@@ -27,6 +27,13 @@ from meshbay_node.transport.webrtc.channel import _get_remote_ip, _pack
from meshbay_node.transport.webrtc.workers import WorkerPool
from meshbay_node.transport.webrtc_server import WebRTCTransport
+from conftest import on_the_daemons_loop
+
+# The pool starts processes, which the loop the suite runs on under Windows
+# cannot do; the node's end of each connection is in one of them, so that
+# loop's reason (two aiortc ends in one process) does not apply here.
+pytestmark = on_the_daemons_loop
+
async def _until(test, seconds: float = 15.0) -> None:
for _ in range(int(seconds / 0.02)):
diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py
index 6f6388e..69eefc8 100644
--- a/packages/meshbay-node/tests/test_packaging_win.py
+++ b/packages/meshbay-node/tests/test_packaging_win.py
@@ -369,6 +369,30 @@ def test_the_build_starts_the_frozen_daemon_not_only_its_help():
assert 'url = "http://127.0.0.1:1"' in smoke
+def test_the_build_starts_a_frozen_connection_process():
+ """Above one `connection_processes` a frozen node starts copies of itself,
+ `meshbay-node.exe webrtc-worker`. No source checkout takes that path and
+ the daemon's smoke test stops short of it: it never signs in to a hub,
+ which is when the processes start."""
+ build = (WIN / "build-node-runtime.ps1").read_text(encoding="utf-8")
+ step = build.split("smoke test: the frozen node as a connection process", 1)[1]
+ assert 'smoke-connection-process.py") $exe (Join-Path $OutDir "meshbay-nodew.exe")' in step
+ assert "if ($LASTEXITCODE -ne 0) { throw" in step
+ # The other end is the node's own modules, in the build interpreter: removed
+ # any earlier, the step has nothing to run with.
+ assert build.count("Remove-Item -Recurse -Force $BuildVenv") == 1
+ assert "Remove-Item -Recurse -Force $BuildVenv" in step
+
+ smoke = (WIN / "smoke-connection-process.py").read_text(encoding="utf-8")
+ assert 'workers._argv = lambda: [exe, "webrtc-worker"]' in smoke
+ # The words a frozen node starts itself with, and the ones it answers to.
+ node = ROOT / "packages" / "meshbay-node" / "src" / "meshbay_node"
+ assert 'return [sys.executable, "webrtc-worker"]' in (
+ node / "transport" / "webrtc" / "workers.py").read_text(encoding="utf-8")
+ assert 'if sys.argv[1:2] == ["webrtc-worker"]:' in (
+ node / "daemon.py").read_text(encoding="utf-8")
+
+
def _run_stop_node(tmp_path, dummy_name, localappdata):
"""stop-node.ps1 as setup runs it, with the process and task names swapped
for ones that belong to this test -- the real ones would stop the