aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-09 01:15:42 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-09 01:15:42 +0200
commit9563dff61b61681fed27673afd7c7b6ed30eb176 (patch)
treefb7d14b47bf685670977127f30058193d378f3d5 /packages/meshbay-node/src
parentb3ea222ca8b6218e0187070fe94bc8fed7059a90 (diff)
downloadmeshbay-9563dff61b61681fed27673afd7c7b6ed30eb176.tar.gz
fix(node): push the grants a per-member cap change produces
Raising the per-member cap from 2 to 4 left both waiting transfers on "waiting". The pool granted them correctly and nobody told the peers: `ops.set_transfer_limits` computed `granted` and never sent a `transfer_state`, where the node-wide path (`WebRTCTransport.set_capacity`) does. That is the first row of §5.2 of ~/next/improve-downloads.md — "node granted a slot, the push was lost" — reached by writing the decision and forgetting the send. It is the same omission as the missing `touch()` call one layer up, on the same day: a mechanism that is right everywhere except at the seam where it has to reach somebody. The client recovered after its 60-second watchdog re-asked, which is why this looked like a slow queue rather than a lost message. `transfer_probe.py --operator` covers it now, separately from the node-wide hot-swap it already covered — different door, different code path, and only one of them was tested. Verified failing with the push removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
Diffstat (limited to 'packages/meshbay-node/src')
-rw-r--r--packages/meshbay-node/src/meshbay_node/ops.py40
1 files changed, 38 insertions, 2 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py
index 088ee32..5b8e22d 100644
--- a/packages/meshbay-node/src/meshbay_node/ops.py
+++ b/packages/meshbay-node/src/meshbay_node/ops.py
@@ -1416,6 +1416,15 @@ async def set_transfer_limits(state: dict, group_id: str,
webrtc = state.get("webrtc")
slots = getattr(webrtc, "_ctx", {}).get("_transfer_slots") if webrtc else None
granted = slots.set_group_limits(group_id, limits) if slots else []
+ # And **tell them**. The node-wide path (`WebRTCTransport.set_capacity`)
+ # does this and this one did not: the leases were granted in the pool and
+ # the peers waiting on them were never told, so a cap raised from 2 to 4
+ # left both transfers sitting at "waiting" until the client's own watchdog
+ # re-asked a minute later. That is §5.2's first row — "node granted a slot,
+ # the push was lost" — reached by writing the grant and forgetting the send,
+ # which is the same omission as the missing `touch()` one layer up.
+ for lease in granted:
+ webrtc._notify_granted(lease)
log.info("Transfer limits for group %s: %s (%d started at once)",
group_id[:8], limits, len(granted))
return {"group_id": group_id, "limits": limits,
@@ -1454,8 +1463,35 @@ async def list_transfers(state: dict) -> dict:
or DEFAULT_MAX_CONCURRENT),
"per_member": DEFAULT_MAX_PER_MEMBER,
"queued": 0}
- for k in KINDS}, "leases": []}
- return slots.snapshot()
+ for k in KINDS}, "leases": [], "groups": _group_limits(state)}
+ out = slots.snapshot()
+ out["groups"] = _group_limits(state)
+ return out
+
+
+def _group_limits(state: dict) -> list[dict]:
+ """Each group's per-member caps, as the operator set them.
+
+ Reported because `transfers show` used to print only the node's default and
+ an operator reading "2 per member" had no way to tell whether that was this
+ group's setting or the fallback — and no way to change it either, since the
+ signed op had no door but MNP. Both were the same bug wearing two faces.
+ """
+ from meshbay_node.transfers import DEFAULT_MAX_PER_MEMBER
+
+ config = state.get("config")
+ groups_ctx = state.get("groups_ctx") or {}
+ out = []
+ for group in (getattr(config, "groups", None) or []):
+ limits = (groups_ctx.get(group.id) or {}).get("transfer_limits") or {}
+ out.append({
+ "group_id": group.id,
+ "name": group.name,
+ "download": int(limits.get("download") or DEFAULT_MAX_PER_MEMBER),
+ "upload": int(limits.get("upload") or DEFAULT_MAX_PER_MEMBER),
+ "set": bool(limits),
+ })
+ return out
# ── Applications ─────────────────────────────────────────────────────────────