diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-09 01:15:42 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-09 01:15:42 +0200 |
| commit | 9563dff61b61681fed27673afd7c7b6ed30eb176 (patch) | |
| tree | fb7d14b47bf685670977127f30058193d378f3d5 /packages/meshbay-node/src/meshbay_node/ops.py | |
| parent | b3ea222ca8b6218e0187070fe94bc8fed7059a90 (diff) | |
| download | meshbay-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/meshbay_node/ops.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/ops.py | 40 |
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 ───────────────────────────────────────────────────────────── |