Skip to content

Commit f3d47d3

Browse files
alex-poorclaude
andcommitted
Expose p2panda's membership knobs, and record why the peer stalls
`Gossip::builder(..).config(..)` was never called, so HyParView ran on iroh-gossip's defaults — active_view_capacity 5, shuffle_interval 60s, the latter commented "Wild guess" upstream. DIASWARM_ACTIVE_VIEW and DIASWARM_SHUFFLE_SECS now expose both, because this should be measured rather than argued about. And records what the upstream source actually says. A sync session is created only from HyParView Joined/NeighbourUp events, per topic, so the peers you exchange data with are exactly your active view; the one API that would let an application sync with a peer it knows holds the data, initiate_session, is cfg(test) behind a TODO. Relay between sessions is same-topic only, despite a doc comment that reads as unconditional. Together those mean finer-grained topics dilute both sampling and relay — which makes D31's dual-topic transition a contributor. ⚠️ And records that the experiment testing the view sizing was confounded: the old binary was already climbing +1 per pass before the change went in, so the baseline was healthy and the result proves nothing. The source findings do not depend on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b971d1c commit f3d47d3

2 files changed

Lines changed: 95 additions & 2 deletions

File tree

‎crates/diaswarm-net/src/swarm.rs‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,37 @@ impl Swarm {
240240
.context("mdns")?;
241241
let discovery =
242242
Discovery::builder(book.clone(), endpoint.clone()).spawn().await.context("discovery")?;
243-
let gossip =
244-
Gossip::builder(book.clone(), endpoint.clone()).spawn().await.context("gossip")?;
243+
// **THE MEMBERSHIP KNOBS, WHICH WERE NEVER CONFIGURED.**
244+
// `Gossip::builder(..).config(..)` takes a `GossipConfig` whose
245+
// `membership` is iroh-gossip's HyParView config, and passing none gave
246+
// us the paper's defaults: `active_view_capacity: 5`,
247+
// `shuffle_interval: 60s` — the latter commented "Wild guess" upstream.
248+
//
249+
// **THIS MATTERS BECAUSE SYNC SESSIONS COME FROM MEMBERSHIP.**
250+
// `spawn_membership_task` initiates a session only on a HyParView
251+
// `Joined` or `NeighbourUp` event, so the set of peers you exchange
252+
// data with is exactly your active view. A peer that is not sampled
253+
// into it is a peer you never sync with, and nothing later corrects
254+
// that on its own.
255+
//
256+
// A pool of a handful of phones should fit inside a capacity of five.
257+
// It is raised anyway, because the cost is a few more connections in a
258+
// network this size and the failure it guards against is a carrier that
259+
// silently holds a stale copy. `DIASWARM_ACTIVE_VIEW` and
260+
// `DIASWARM_SHUFFLE_SECS` exist so this can be measured rather than
261+
// argued about.
262+
let mut gossip_config = p2panda_net::gossip::GossipConfig::default();
263+
if let Ok(n) = std::env::var("DIASWARM_ACTIVE_VIEW").unwrap_or_default().parse::<usize>() {
264+
gossip_config.membership.active_view_capacity = n;
265+
}
266+
if let Ok(secs) = std::env::var("DIASWARM_SHUFFLE_SECS").unwrap_or_default().parse::<u64>() {
267+
gossip_config.membership.shuffle_interval = std::time::Duration::from_secs(secs);
268+
}
269+
let gossip = Gossip::builder(book.clone(), endpoint.clone())
270+
.config(gossip_config)
271+
.spawn()
272+
.await
273+
.context("gossip")?;
245274

246275
// The vault protocol on p2panda's endpoint. Built before it is handed
247276
// over so the offer window stays reachable from here.

‎docs/migration.md‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,3 +2957,67 @@ disk is the cost.
29572957

29582958
Not investigated. Recorded because the measurement exists now and will not
29592959
after the process restarts.
2960+
2961+
### 🔍 Why the peer stalled: what the upstream source says, and what it does not
2962+
2963+
Chased through `p2panda-net` and `p2panda-sync` 0.7.1 after the desktop peer
2964+
caught up once and then never again.
2965+
2966+
**1. A sync session is created only from HyParView membership events.**
2967+
`sync/actors/manager.rs::spawn_membership_task` derives a *separate gossip
2968+
overlay per sync topic* and initiates a session on exactly two events:
2969+
2970+
```rust
2971+
GossipEvent::Joined { topic, nodes } => InitiateSync
2972+
GossipEvent::NeighbourUp { node, topic } => InitiateSync
2973+
```
2974+
2975+
So **the peers you exchange data with are exactly your active view**, per topic.
2976+
There is no other path: `SyncHandle::initiate_session(node_id)` exists, does
2977+
precisely "sync with this peer", and is `#[cfg(test)]` behind a TODO asking
2978+
whether to make it public.
2979+
2980+
**2. Relay between sessions is same-topic only.** The protocol doc says live
2981+
messages are "forwarded to any concurrently running sync sessions", which reads
2982+
as unconditional. `manager/event_stream.rs` shows it is not:
2983+
2984+
```rust
2985+
let topic = state.session_topic_map.topic(session_id);
2986+
let keys = state.session_topic_map.sessions(topic); // same topic only
2987+
```
2988+
2989+
Two peers can be connected and never relay, if their sessions are on different
2990+
topics.
2991+
2992+
**3. Together, these mean topic proliferation dilutes connectivity.** Each topic
2993+
is its own overlay with its own sampling, and relay cannot bridge across topics.
2994+
Finer-grained topics — which feel tidier — make both mechanisms sparser. The
2995+
[D31](decisions.md) transition, which carries every subject at *two* topics
2996+
during the migration, doubles the overlay count and therefore halves the density
2997+
of each.
2998+
2999+
#### The knobs that were never configured
3000+
3001+
`Gossip::builder(..).config(GossipConfig)` was never called, so membership ran
3002+
on iroh-gossip's defaults: `active_view_capacity: 5`, `shuffle_interval: 60s`
3003+
— the latter commented **"Wild guess"** upstream. `DIASWARM_ACTIVE_VIEW` and
3004+
`DIASWARM_SHUFFLE_SECS` now expose both.
3005+
3006+
⚠️ **AND THE EXPERIMENT THAT WOULD HAVE TESTED THEM WAS CONFOUNDED, WHICH IS THE
3007+
POINT OF THIS SECTION.** Raising the active view to 16 appeared to produce a
3008+
peer climbing `+1` per pass with no stall. The log timestamps say otherwise:
3009+
3010+
```
3011+
09:09:44 pid 1614426 (OLD binary, default config) holding 1795
3012+
09:11:44 pid 1614426 holding 1797 ← already +1/pass
3013+
09:12:33 pid 1698373 (NEW, active_view=16) starting
3014+
```
3015+
3016+
**The baseline was already healthy.** The stall is intermittent, and measuring a
3017+
fix against a period when the fault was absent proves nothing. A real test needs
3018+
the stall reproducible on demand, or hours of A/B — not two lines that came from
3019+
the binary without the change.
3020+
3021+
**Nothing in points 1–3 depends on that experiment.** They are read from
3022+
upstream source and stand on their own; the view sizing remains a plausible
3023+
contributor and an unproven one.

0 commit comments

Comments
 (0)