From 9563dff61b61681fed27673afd7c7b6ed30eb176 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 9 Sep 2026 01:15:42 +0200 Subject: fix(node): push the grants a per-member cap change produces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST --- packages/meshbay-node/src/meshbay_node/ops.py | 40 +++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-node/src/meshbay_node/ops.py') 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 ───────────────────────────────────────────────────────────── -- cgit v1.2.3