{
  "summary": "Dependency source establishes several synchronous, unbounded-in-wall-time Windows operations on endpoint-creation/background-task workers: GetAdaptersAddresses during netmon initialization and portmapper gateway lookup, socket/multicast setup, and CancelMibChangeNotify2 during monitor destruction. It does NOT identify which caused the observed stalls. WMI route queries and ordinary netwatch socket destruction are explicitly offloaded. Moving only outbound DialPlan futures cannot isolate existing endpoint background actors or their destruction.",
  "files": [
    {
      "path": "C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/iroh-0.98.2/src/socket.rs",
      "description": "Bind sequence, spawned endpoint actor, close/abort and monitor ownership; relevant lines 206–215, 842–1082, 1098–1199."
    },
    {
      "path": "C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/netwatch-0.16.0/src/interfaces.rs",
      "description": "State::new performs synchronous interface enumeration before asynchronous route lookup, lines 251–287; HomeRouter synchronous gateway lookup, 444–473."
    },
    {
      "path": "C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/netwatch-0.16.0/src/netmon/windows.rs",
      "description": "Windows callback registration and synchronous cancellation during destruction, lines 32–51, 72–94, 127–181."
    },
    {
      "path": "C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/netdev-0.42.0/src/os/windows/interface.rs",
      "description": "GetAdaptersAddresses(AF_UNSPEC), up to four calls on buffer overflow, no time bound; lines 90–128."
    },
    {
      "path": "C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/portmapper-0.16.0/src/lib.rs",
      "description": "Service spawned on current runtime, synchronous gateway enumeration before async protocol work; lines 170–177, 631–639, 734–760, 772–794."
    },
    {
      "path": "C:/Users/decid/.cargo/git/checkouts/swarm-discovery-dcbf738e81529f91/89a2200/src/lib.rs",
      "description": "Actual resolved patched discovery source, not registry copy; runtime-entered synchronous socket setup and abort-only guard destruction, lines 378–410, 494–497."
    }
  ],
  "architecture": "Builder::bind polls synchronous transport setup and netmon initialization on its caller worker, spawning portmapper, QUIC/endpoint and netmon actors on the current Tokio runtime. mDNS captures Handle::current and spawns its actors there. Endpoint abort cancels/aborts tasks rather than joining; their destructors subsequently execute on the runtime owning those tasks. Iroh's Runtime is a task tracker/cancellation wrapper, not a separate thread pool.",
  "report": "## Source identity\nLet R = C:/Users/decid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f and G = C:/Users/decid/.cargo/git/checkouts/swarm-discovery-dcbf738e81529f91/89a2200. All citations below expand these exact prefixes. Cargo.lock:1981–1984,2475–2478,2557–2560,3044–3047 resolves iroh 0.98.2, netdev 0.42.0, netwatch 0.16.0, portmapper 0.16.0. Cargo.lock:4361–4363 resolves swarm-discovery 0.6.0-alpha.2 to git commit 89a2200d54a4e3cab2f46cc75ebff49a1fb07614, NOT the installed registry copy. Findings describe current resolved source; equivalence to the shipped v0.70.0 binary requires its build provenance.\n\n## Bind and background ownership — facts\n- R/iroh-0.98.2/src/endpoint.rs:205–263 performs configuration synchronously then awaits EndpointInner::bind; :276–285 creates/adds address lookup services synchronously after inner bind. An async function/await does not offload the synchronous prefixes below.\n- R/iroh-0.98.2/src/socket.rs:866 creates portmapper; :906–912 synchronously binds transports; :997–1004 creates QUIC endpoint and awaits Monitor::new; :1069–1080 spawns the socket actor. R/netwatch-0.16.0/src/netmon.rs:63–75 initializes then spawns its monitor actor. R/n0-future-0.3.2/src/task.rs:4–9 directly reexports Tokio spawn/AbortOnDropHandle on native targets.\n- R/portmapper-0.16.0/src/lib.rs:170–177 uses tokio::spawn for its service. R/iroh-0.98.2/src/address_lookup/mdns.rs:280–298 captures Handle::current; :461–464 spawns its address-lookup actor. G/src/lib.rs:378–404 enters the supplied handle, synchronously creates sockets, and creates the guardian actor using that handle.\n- R/iroh-0.98.2/src/runtime.rs:23–32,79–96 is a TaskTracker/cancellation wrapper, not another Tokio runtime. Existing spawned actors do not migrate when a caller later polls a dial future elsewhere. Caveat: its spawn method uses the then-current runtime rather than storing an original handle, so do not claim every future driver necessarily has immutable creation-runtime selection merely from this wrapper.\n\n## Candidate synchronous segments and bounds\n1. Interface enumeration is NOT offloaded. Monitor::new → Actor::new (R/netwatch-0.16.0/src/netmon/actor.rs:64–69) → State::new → netdev::interface::get_interfaces (R/netwatch-0.16.0/src/interfaces.rs:251–287, specifically :256). R/netdev-0.42.0/src/os/windows/interface.rs:90–128 calls GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_GATEWAYS), starting with 15KB capacity and allowing three overflow retries: maximum four calls per enumeration, but NO elapsed-time deadline and NO yielding/offload around the calls. Interface sorting/traversal follows synchronously. IPv4-only transport selection does not change this AF_UNSPEC enumeration.\n2. Additional enumeration occurs asynchronously relative to bind, but synchronously on a worker: portmapper Service::get_mapping (:631–639) and probe_request (:734–760) call ip_and_gateway (:772–794), which calls HomeRouter::new. R/netwatch-0.16.0/src/interfaces.rs:444–473 calls netdev::get_default_gateway; R/netdev-0.42.0/src/route/mod.rs:9–22 calls get_interfaces. Neither layer uses spawn_blocking. It happens before spawning protocol probe/mapping futures. R/iroh-0.98.2/src/socket.rs:914–925 sends the newly bound local port to portmapper. Portmapper is a default iroh feature (R/iroh-0.98.2/Cargo.toml:49–54); runtime Disabled config would bypass it (src/portmapper.rs:35–49). Thus participation remains conditional on actual caller configuration.\n3. UDP setup is synchronous: R/iroh-0.98.2/src/socket/transports/ip.rs:164–172 calls netwatch::UdpSocket::bind_full; :404–444 processes configurations serially. R/netwatch-0.16.0/src/udp.rs:724–761 does socket creation, buffer options, IPv6-only option when applicable, bind, nonblocking setup, Tokio registration and noq UDP-state initialization. No time limit/offload protects these OS calls. Nonblocking I/O after construction does not make construction asynchronous.\n4. mDNS initialization similarly does synchronous socket creation/bind/multicast membership/options: G/src/socket.rs:94–161; Sockets::new :228–265 loops explicit interfaces then creates sockets according to IpClass. Stock iroh mDNS explicitly chooses IpClass::Auto (R/iroh-0.98.2/src/address_lookup/mdns.rs:506–517), so an IPv4-only endpoint transport gate does not itself imply IPv4-only mDNS socket setup. No initialization timeout/offload is present. These are direct Tokio sockets, not netwatch's offloaded-close wrapper.\n5. Old-endpoint cleanup has a particularly relevant synchronous wait candidate: Monitor owns AbortOnDropHandle (R/netwatch-0.16.0/src/netmon.rs:69–75), its actor owns RouteMonitor (src/netmon/actor.rs:46–55), and Windows RouteMonitor owns CallbackHandler. CallbackHandler::drop unregisters unicast and route callbacks sequentially (src/netmon/windows.rs:72–94), directly calling CancelMibChangeNotify2 (:127–143 and :169–181). No timeout or spawn_blocking protects either call. Microsoft's API documentation explicitly describes deadlock if cancellation runs on the callback thread or owns a resource the callback requires: https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-cancelmibchangenotify2 . This supports a callback-quiescence blocking hazard; it does NOT establish that these callbacks actually deadlock in the rig. Registered closures here use try_send, not a waiting channel send (src/netmon/windows.rs:36–48). Registration itself also directly calls NotifyUnicastIpAddressChange/NotifyRouteChange2 (:112–119,:154–161).\n6. Ordinary blocking locks exist: address services.clear takes std RwLock and drops services under it (R/iroh-0.98.2/src/address_lookup.rs:543–546); endpoint abort takes actor_task Mutex (src/socket.rs:1195); netwatch rebind holds socket RwLock across synchronous bind (R/netwatch-0.16.0/src/udp.rs:81–91). Their source supplies no acquisition timeout, but no observed contention or lock cycle proves them responsible.\n\n## Important non-culprits / limits of timing arguments\n- Windows WMI route query IS offloaded: R/netwatch-0.16.0/src/interfaces/windows.rs:29–46 constructs WMIConnection and filtered query inside get_default_route, invoked via spawn_blocking(...).await. It can delay bind completion with no wrapper deadline, but a pending blocking-pool JoinHandle alone does not monopolize a Tokio worker. Do not conflate it with the preceding synchronous GetAdaptersAddresses.\n- netwatch UDP destructor explicitly converts its socket and spawn_blocking-drops it: R/netwatch-0.16.0/src/udp.rs:862–873. Therefore normal netwatch UDP close is not an unoffloaded worker closesocket accusation. Socket lock/conversion happens first; other raw sockets/error paths are separate.\n- EndpointInner Drop calls abort if not closed (R/iroh-0.98.2/src/socket.rs:206–215). abort (:1178–1199) clears lookups, cancels tokens, aborts runtime and drops actor handle; it does not join a task. Runtime::abort only signals cancellation/closes tracker (src/runtime.rs:50–55). Abort is not preemption of an in-progress blocking OS call; subsequent task destruction can execute synchronous destructors such as monitor cancellation.\n- Graceful close waits asynchronously for QUIC idle, then has a 100ms actor grace timeout, then awaits runtime task completion (src/socket.rs:1110–1157). The comment's usual ~3s QUIC probe timeout is not a hard total close bound; the 100ms timeout only bounds the actor-await segment cooperatively, not OS calls/destructors. It cannot justify a claim of a synchronous 3s close sleep.\n- Actual patched swarm DropGuard only aborts guardian (G/src/lib.rs:494–497), no synchronous join. Its old dead-updater retry issue is patched: G/src/updater.rs:19–23 exits retry when me.is_gone(); do not attribute current stalls to the unpatched registry version. Async sleep/protocol waits are not worker-blocking evidence. Portmapper's UPnP search=1s, PCP/NAT-PMP receive=500ms constants (R/portmapper-0.16.0/src/lib.rs:28–35) do not bound its earlier gateway enumeration.\n\n## RCA inference and falsifier/remedy boundary\n[INFERENCE] Three concurrent subnet replacements on a two-worker runtime can overlap any two of the synchronous segments above, preventing the canary and logging tasks from running despite zero active application dial futures. In particular, spawned portmapper work and delayed destruction of old monitor actors need not run at the exact bind log instant. This is compatible with, but does not explain quantitatively or prove, the 5–10s post-PAIR_MEET_UP peaks. Source contains no demonstrated deterministic 5–10s blocking timer here.\n\nNarrow falsifier: capture BOTH NetHost worker stacks during a stale-canary interval, correlated to begin/end durations of enumeration, callback deregistration, socket/mDNS setup and endpoint retirement. The two-worker synchronous-monopolization hypothesis requires both workers to be occupied/nonprogressing, or one blocked with the other prevented by a demonstrated dependent resource/scheduling condition. If both are freely servicing other tasks, or all these segments complete outside the peak and stacks show unrelated work, this proposed dependency mechanism is falsified. A long asynchronous bind/WMI/protocol duration alone is not confirmation.\n\nMoving only DialPlan futures would not relocate bind-scoped synchronous creation, existing socket/netmon/portmapper/mDNS actors, nor the owning runtime's aborted-future cleanup. It could affect newly spawned work using current runtime, but is not endpoint-lifecycle isolation. Remedy selection must follow the observed blocking segment: offloading a proven enumeration/cancellation boundary and isolating complete endpoint lifecycle are different scopes; neither is established as the required remedy by source alone.\n\nNo commands, builds, tests, validation, edits or product mutations performed; investigation used source reads/search and one official Windows API reference."
}
