aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/config.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-08 14:53:03 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-08 14:53:03 +0200
commit4b94468d24913c3071b48eeefb43367f4f5cd523 (patch)
treea0072ff4b986e6aa9c6dd14d6f54d0da51e3d72f /packages/meshbay-node/src/meshbay_node/config.py
parentbdeffa448cdde9680fbf7bdda036746b101ddf75 (diff)
downloadmeshbay-4b94468d24913c3071b48eeefb43367f4f5cd523.tar.gz
feat(node): make the transfer caps settable, node-wide and per group
Step 3 of ~/next/improve-downloads.md. Step 2 built the pools with constants; this gives them to the operator, in the two scopes they belong to. **The pools are the machine's.** `[node] max_concurrent_downloads` and `max_concurrent_uploads`, default 8, on the §2.11 pattern: node.toml for a fresh install, a roster.db override for immediate effect, editable from the Node page and from `meshbay-node transfers show|set`, applied live through the one `set_capacity` step 1 fixed. **The per-member cap is a group's.** How many transfers one member may run at once here — on the node like every other group setting (not the hub, which would have authority over someone else's disk; not node.toml, which is hand-written and needs a restart), changed by a signed operator instruction (`OP_TRANSFER_LIMITS`, subject "d=2,u=2" so what is signed names the outcome), broadcast to the group, and read live by the pools. That was the one thing step 2's shape could not express: `per_member` was a single node-wide number. `group_limits` and `member_cap(kind, member)` make it a lookup — the group's own value if it has one, the node's default otherwise — and it is deliberately the only dimension that is not node-wide. Three refusals, each with a test: - **absent means the default (2), never "unlimited".** A group that predates the setting coming back unlimited would leave the node-wide pool as the only control, which is the situation slots exist to end; - **zero is not "unlimited"**, and is not "this member may not transfer" either: the floor is one everywhere, and the CLI says to revoke the member instead; - **an unreadable row reads as unset**, not as zero — the same discipline the sealed messages follow, where a payload that does not open must never become a default state on its own. `handshake_ack` carries this member's own caps for this group, so the interface can say "2 of your 2 slots are busy" instead of drawing a bare spinner. Absent reads as "no limit known" and the hint is not drawn — never as "unlimited", which would have the interface contradicting the node. 1164 node, 793 hub, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/config.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/config.py21
1 files changed, 21 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py
index 7673a51..7351b1c 100644
--- a/packages/meshbay-node/src/meshbay_node/config.py
+++ b/packages/meshbay-node/src/meshbay_node/config.py
@@ -60,6 +60,13 @@ device_request_ttl_minutes = 60
# lower it on a Pi.
max_concurrent_streams = 8
+# How many downloads and uploads run at once on this node, across every group.
+# A slot is concurrency, not bandwidth: what it protects is open file handles,
+# disk seeks and the channel buffer each transfer keeps full. Past this, a
+# member is queued and told so, and starts when a slot frees.
+max_concurrent_downloads = 8
+max_concurrent_uploads = 8
+
# HEVC sources have no browser decoder on most platforms, so streaming one is
# transcoded to H264 rather than the usual free copy — real CPU per viewer.
# Set to false only if every viewer's client is known to decode HEVC itself.
@@ -157,6 +164,14 @@ class NodeConfig:
# the node answers "server busy" — see MAX_CONCURRENT_TRANSCODES in
# transport/webrtc_server.py for what one costs.
max_concurrent_streams: int = 8
+ # How many transfers run at once on this node, across every group —
+ # separate pools, because a download and an upload cost different things
+ # and one queue for both makes each cap meaningless. Streaming has its own
+ # third pool (max_concurrent_streams above): a member watching a film is
+ # not charged a download slot, and a download does not make the next film
+ # answer "server busy". See meshbay_node/transfers.py.
+ max_concurrent_downloads: int = 8
+ max_concurrent_uploads: int = 8
# HEVC (and any future codec in media_probe.py's
# BROWSER_INCOMPATIBLE_VIDEO_CODECS) has no decoder in most browsers, so
# streaming it needs a real re-encode to H264 rather than the usual free
@@ -332,6 +347,12 @@ def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config:
cfg.node.max_concurrent_streams = _positive(
nd.get("max_concurrent_streams", cfg.node.max_concurrent_streams),
cfg.node.max_concurrent_streams, "max_concurrent_streams")
+ cfg.node.max_concurrent_downloads = _positive(
+ nd.get("max_concurrent_downloads", cfg.node.max_concurrent_downloads),
+ cfg.node.max_concurrent_downloads, "max_concurrent_downloads")
+ cfg.node.max_concurrent_uploads = _positive(
+ nd.get("max_concurrent_uploads", cfg.node.max_concurrent_uploads),
+ cfg.node.max_concurrent_uploads, "max_concurrent_uploads")
cfg.node.transcode_incompatible_video = bool(
nd.get("transcode_incompatible_video", cfg.node.transcode_incompatible_video))
ice_if = nd.get("ice_interfaces")