Skip to content

Commit f07effc

Browse files
committed
refactor(iroh)!: redesign path observation API
1 parent 8e10c37 commit f07effc

16 files changed

Lines changed: 807 additions & 720 deletions

File tree

‎iroh/examples/custom-transport.rs‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use iroh::{
1111
test_utils::test_transport::{TEST_TRANSPORT_ID, TestNetwork, TestTransport},
1212
};
1313
use n0_error::{Result, StdResultExt};
14-
use n0_watcher::Watcher;
1514

1615
/// Each protocol is identified by its ALPN string.
1716
///
@@ -124,7 +123,7 @@ async fn main() -> Result<()> {
124123

125124
// Helper to print paths and verify test transport is selected
126125
let verify_test_transport = |label: &str| {
127-
let paths = conn.paths().get();
126+
let paths = conn.paths();
128127
println!("Paths {}:", label);
129128
for path in paths.iter() {
130129
println!(
@@ -134,16 +133,16 @@ async fn main() -> Result<()> {
134133
path.rtt()
135134
);
136135
}
137-
let selected_path = paths.iter().find(|p| p.is_selected());
138-
let is_test_transport = selected_path.is_some_and(|p| {
136+
let selected_path = paths.selected();
137+
let is_test_transport = selected_path.as_ref().is_some_and(|p| {
139138
matches!(p.remote_addr(), TransportAddr::Custom(addr) if addr.id() == TEST_TRANSPORT_ID)
140139
});
141140
assert!(
142141
is_test_transport,
143142
"Expected test transport (id={}) to be selected {}, got: {:?}",
144143
TEST_TRANSPORT_ID,
145144
label,
146-
selected_path.map(|p| p.remote_addr())
145+
selected_path.as_ref().map(|p| p.remote_addr())
147146
);
148147
println!(
149148
"Verified: test transport (id={}) is selected {}",

‎iroh/examples/monitor-connections.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{sync::Arc, time::Duration};
22

33
use iroh::{
4-
Endpoint, Watcher,
4+
Endpoint,
55
endpoint::{
66
AfterHandshakeOutcome, Closed, Connection, EndpointHooks, WeakConnectionHandle, presets,
77
},
@@ -133,7 +133,7 @@ impl Monitor {
133133
Some(MonitoredConnection { alpn, remote_id, handle }) = rx.recv() => {
134134
let alpn = String::from_utf8_lossy(&alpn).to_string();
135135
let remote = remote_id.fmt_short();
136-
let rtt = handle.upgrade().and_then(|c| c.paths().peek().iter().map(|p| p.stats().expect("conn is not dropped").rtt).min());
136+
let rtt = handle.upgrade().and_then(|c| c.paths().iter().map(|p| p.rtt()).min());
137137
info!(%remote, %alpn, ?rtt, "new connection");
138138
tasks.spawn(async move {
139139
match handle.closed().await {

‎iroh/examples/remote-info.rs‎

Lines changed: 65 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ fn log_active(remote_map: &RemoteMap) {
101101
"[{}] is_active {}, connections {}, ip_path {:?}, relay_path {:?}, current_min_rtt {:?}",
102102
id.fmt_short(),
103103
info.is_active(),
104-
info.connections().count(),
104+
info.active_connections(),
105105
info.has_ip_path(),
106106
info.has_relay_path(),
107-
info.current_min_rtt()
107+
info.current_min_rtt(),
108108
);
109109
}
110110
}
@@ -126,7 +126,7 @@ fn log_aggregate(remote_map: &RemoteMap) {
126126
aggregate.relay_path,
127127
SystemTime::now()
128128
.duration_since(aggregate.last_update)
129-
.unwrap()
129+
.unwrap_or_default()
130130
);
131131
}
132132
}
@@ -166,14 +166,13 @@ mod remote_map {
166166
};
167167

168168
use iroh::{
169-
EndpointId, Watcher,
169+
EndpointId, TransportAddr,
170170
endpoint::{
171-
AfterHandshakeOutcome, Connection, EndpointHooks, PathInfo, WeakConnectionHandle,
171+
AfterHandshakeOutcome, Connection, EndpointHooks, PathEvent, WeakConnectionHandle,
172172
},
173173
};
174-
use n0_future::task::AbortOnDropHandle;
174+
use n0_future::{StreamExt, task::AbortOnDropHandle};
175175
use tokio::{sync::mpsc, task::JoinSet};
176-
use tokio_stream::StreamExt;
177176
use tracing::{Instrument, debug, info, info_span};
178177

179178
/// Information about a remote info.
@@ -211,19 +210,13 @@ mod remote_map {
211210
}
212211

213212
impl Aggregate {
214-
fn update(&mut self, path: &PathInfo) {
213+
fn update(&mut self, addr: TransportAddr, stats: iroh::endpoint::PathStats) {
215214
self.last_update = SystemTime::now();
216-
if path.is_ip() {
217-
self.ip_path = true;
218-
}
219-
if path.is_relay() {
220-
self.relay_path = true;
221-
}
222-
if let Some(stats) = path.stats() {
223-
debug!("path update addr {:?} {stats:?}", path.remote_addr());
224-
self.rtt_min = self.rtt_min.min(stats.rtt);
225-
self.rtt_max = self.rtt_max.max(stats.rtt);
226-
}
215+
self.ip_path |= addr.is_ip();
216+
self.relay_path |= addr.is_relay();
217+
debug!("path update {addr} {stats:?}");
218+
self.rtt_min = self.rtt_min.min(stats.rtt);
219+
self.rtt_max = self.rtt_max.max(stats.rtt);
227220
}
228221
}
229222

@@ -239,46 +232,37 @@ mod remote_map {
239232
///
240233
/// Returns `None` if there are no active connections.
241234
pub fn current_min_rtt(&self) -> Option<Duration> {
242-
self.connections()
243-
.flat_map(|c| c.upgrade())
244-
.flat_map(|c| c.paths().get().into_iter())
245-
.flat_map(|p| p.stats())
246-
.map(|s| s.rtt)
235+
self.upgraded()
236+
.filter_map(|c| c.paths().iter().map(|p| p.rtt()).min())
247237
.min()
248238
}
249239

250240
/// Returns whether any active connection to the remote has an active IP path.
251-
///
252-
/// Returns `None` if there are no active connections.
253-
pub fn has_ip_path(&self) -> Option<bool> {
254-
self.connections()
255-
.flat_map(|c| c.upgrade())
256-
.flat_map(|c| c.paths().get())
257-
.filter(|path| path.is_ip())
258-
.map(|_| true)
259-
.next()
241+
pub fn has_ip_path(&self) -> bool {
242+
self.upgraded().any(|c| c.paths().iter().any(|p| p.is_ip()))
260243
}
261244

262245
/// Returns whether any active connection to the remote has an active relay path.
263-
///
264-
/// Returns `None` if there are no active connections.
265-
pub fn has_relay_path(&self) -> Option<bool> {
266-
self.connections()
267-
.flat_map(|c| c.upgrade())
268-
.flat_map(|c| c.paths().get())
269-
.filter(|path| path.is_relay())
270-
.map(|_| true)
271-
.next()
246+
pub fn has_relay_path(&self) -> bool {
247+
self.upgraded()
248+
.any(|c| c.paths().iter().any(|p| p.is_relay()))
272249
}
273250

274-
/// Returns `true` if there are active connections to this node.
251+
/// Returns `true` if there are active connections to this remote.
275252
pub fn is_active(&self) -> bool {
276-
!self.connections.is_empty()
253+
self.active_connections() > 0
277254
}
278255

279-
/// Returns an iterator over [`WeakConnectionHandle`] for currently active connections to this remote.
280-
pub fn connections(&self) -> impl Iterator<Item = &WeakConnectionHandle> {
281-
self.connections.values()
256+
/// Returns the number of active connections to this remote.
257+
pub fn active_connections(&self) -> usize {
258+
self.upgraded().count()
259+
}
260+
261+
/// Returns an iterator over all active handles upgraded to a [`Connection`].
262+
fn upgraded(&self) -> impl Iterator<Item = Connection> {
263+
self.connections
264+
.values()
265+
.filter_map(WeakConnectionHandle::upgrade)
282266
}
283267
}
284268

@@ -368,18 +352,14 @@ mod remote_map {
368352
// Main loop
369353
loop {
370354
tokio::select! {
371-
conn = rx.recv() => {
372-
match conn {
373-
Some(conn) => {
374-
conn_id += 1;
375-
Self::on_connection(&mut tasks, map.clone(), conn_id, conn);
376-
},
377-
None => break,
378-
}
355+
Some(conn) = rx.recv() => {
356+
conn_id += 1;
357+
Self::on_connection(&mut tasks, map.clone(), conn_id, conn);
379358
}
380359
Some(res) = tasks.join_next(), if !tasks.is_empty() => {
381360
res.expect("conn close task panicked");
382361
}
362+
else => break,
383363
}
384364
}
385365

@@ -417,34 +397,42 @@ mod remote_map {
417397
let map = map.clone();
418398
async move {
419399
handle.closed().await;
420-
{
421-
let mut inner = map.write().expect("poisoned");
422-
let info = inner.entry(remote_id).or_default();
423-
info.connections.remove(&conn_id);
424-
info.aggregate.last_update = SystemTime::now();
425-
}
400+
let mut inner = map.write().expect("poisoned");
401+
let info = inner.entry(remote_id).or_default();
402+
info.connections.remove(&conn_id);
403+
info.aggregate.last_update = SystemTime::now();
426404
}
427405
.instrument(tracing::Span::current())
428406
});
429407

430408
// Track path changes to update stats aggregate.
431-
if let Some(watcher) = conn.handle.upgrade().map(|c| c.paths()) {
432-
let remote_id = conn.remote_id;
433-
tasks.spawn({
409+
if let Some(mut path_events) = conn.handle.upgrade().map(|conn| conn.path_events()) {
410+
tasks.spawn(
434411
async move {
435-
let mut path_updates = watcher.stream();
436-
while let Some(paths) = path_updates.next().await {
437-
{
438-
let mut inner = map.write().expect("poisoned");
439-
let info = inner.entry(remote_id).or_default();
440-
for path in paths {
441-
info.aggregate.update(&path);
412+
while let Some(event) = path_events.next().await {
413+
let mut inner = map.write().expect("poisoned");
414+
let info = inner.entry(conn.remote_id).or_default();
415+
match event {
416+
PathEvent::Closed {
417+
remote_addr,
418+
last_stats,
419+
..
420+
} => {
421+
info.aggregate.update(remote_addr, *last_stats);
422+
}
423+
_ => {
424+
if let Some(conn) = conn.handle.upgrade() {
425+
for path in conn.paths().iter() {
426+
info.aggregate
427+
.update(path.remote_addr().clone(), path.stats());
428+
}
429+
}
442430
}
443431
}
444432
}
445433
}
446-
.instrument(tracing::Span::current())
447-
});
434+
.instrument(tracing::Span::current()),
435+
);
448436
}
449437
}
450438

@@ -459,8 +447,10 @@ mod remote_map {
459447
let mut inner = map.write().expect("poisoned");
460448
inner.retain(|_remote, info| {
461449
info.is_active()
462-
|| now.duration_since(info.aggregate().last_update).unwrap()
463-
< retention_time
450+
|| now
451+
.duration_since(info.aggregate().last_update)
452+
.map(|age| age < retention_time)
453+
.unwrap_or(true)
464454
});
465455
}
466456
}

0 commit comments

Comments
 (0)