Skip to content

Commit 27f9b06

Browse files
author
“ramfox”
committed
replace abandoned and usable fields with one PathStatus field
also, rename `insert` to `insert_open_path` and mark the `path_state.status = PathStatus::Open`
1 parent 43093c7 commit 27f9b06

File tree

2 files changed

+119
-70
lines changed

2 files changed

+119
-70
lines changed

iroh/src/magicsock/remote_map/remote_state.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,7 @@ impl RemoteStateActor {
451451
path.set_status(status).ok();
452452
conn_state.add_open_path(path_remote.clone(), PathId::ZERO);
453453
self.paths
454-
.insert(path_remote.clone(), Source::Connection { _0: Private });
455-
self.paths.opened_path(&path_remote);
454+
.insert_open_path(path_remote.clone(), Source::Connection { _0: Private });
456455
self.select_path();
457456

458457
if path_remote_is_ip {
@@ -770,8 +769,7 @@ impl RemoteStateActor {
770769
);
771770
conn_state.add_open_path(path_remote.clone(), path_id);
772771
self.paths
773-
.insert(path_remote.clone(), Source::Connection { _0: Private });
774-
self.paths.opened_path(&path_remote);
772+
.insert_open_path(path_remote.clone(), Source::Connection { _0: Private });
775773
}
776774

777775
self.select_path();

iroh/src/magicsock/remote_map/remote_state/path_state.rs

Lines changed: 117 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,30 @@ pub(super) struct RemotePathState {
3434
pending_resolve_requests: VecDeque<oneshot::Sender<Result<(), DiscoveryError>>>,
3535
}
3636

37+
/// Describes the usability of this path, i.e. whether it has ever been opened,
38+
/// when it was closed, or if it has never been usable.
39+
#[derive(Debug, Default)]
40+
pub(super) enum PathStatus {
41+
/// This path is open and active.
42+
Open,
43+
/// This path was once opened, but was abandoned at the given [`Instant`].
44+
Inactive(Instant),
45+
/// This path was never usable and was abandoned at the given [`Instant`].
46+
Unusable,
47+
/// We have not yet attempted holepunching, or holepunching is currently in
48+
/// progress, so we do not know the usability of this path.
49+
#[default]
50+
Unknown,
51+
}
52+
3753
impl RemotePathState {
38-
/// Insert a new address into our list of potential paths.
54+
/// Insert a new address of an open path into our list of paths.
3955
///
40-
/// This will emit pending resolve requests and trigger pruning paths..
41-
pub(super) fn insert(&mut self, addr: transports::Addr, source: Source) {
42-
self.paths
43-
.entry(addr)
44-
.or_default()
45-
.sources
46-
.insert(source.clone(), Instant::now());
56+
/// This will emit pending resolve requests and trigger pruning paths.
57+
pub(super) fn insert_open_path(&mut self, addr: transports::Addr, source: Source) {
58+
let state = self.paths.entry(addr).or_default();
59+
state.status = PathStatus::Open;
60+
state.sources.insert(source.clone(), Instant::now());
4761
self.emit_pending_resolve_requests(None);
4862
self.prune_paths();
4963
}
@@ -54,22 +68,18 @@ impl RemotePathState {
5468
/// `RemotePathState`
5569
pub(super) fn abandoned_path(&mut self, addr: &transports::Addr) {
5670
if let Some(state) = self.paths.get_mut(addr) {
57-
state.abandoned = Some(Instant::now());
58-
}
59-
}
60-
61-
/// Mark a path as opened.
62-
///
63-
/// If this path does not exist, it does nothing to the
64-
/// `RemotePathState`
65-
pub(super) fn opened_path(&mut self, addr: &transports::Addr) {
66-
if let Some(state) = self.paths.get_mut(addr) {
67-
state.usable = true;
68-
state.abandoned = None;
71+
match state.status {
72+
PathStatus::Open | PathStatus::Inactive(_) => {
73+
state.status = PathStatus::Inactive(Instant::now());
74+
}
75+
PathStatus::Unusable | PathStatus::Unknown => {
76+
state.status = PathStatus::Unusable;
77+
}
78+
}
6979
}
7080
}
7181

72-
/// Inserts multiple addresses into our list of potential paths.
82+
/// Inserts multiple addresses of unknown status into our list of potential paths.
7383
///
7484
/// This will emit pending resolve requests and trigger pruning paths.
7585
pub(super) fn insert_multiple(
@@ -165,26 +175,15 @@ pub(super) struct PathState {
165175
/// We keep track of only the latest [`Instant`] for each [`Source`], keeping the size
166176
/// of the map of sources down to one entry per type of source.
167177
pub(super) sources: HashMap<Source, Instant>,
168-
/// The last time this path was proven usable.
169-
///
170-
/// If this is `false` and the `abandoned` field is `Some`, than we attempted
171-
/// to open this path, but it did not work.
172-
///
173-
/// If this is `false` and the `abandoned` field is `None`, than we do not know
174-
/// yet if this path is usable.
175-
pub(super) usable: bool,
176-
/// The last time a path with this addr was abandoned.
177-
///
178-
/// If this is `Some` and usable is `None`, than we attempted to use this path and it
179-
/// did not work.
180-
pub(super) abandoned: Option<Instant>,
178+
/// The usability status of this path.
179+
pub(super) status: PathStatus,
181180
}
182181

183182
/// Prunes the IP paths in the paths HashMap.
184183
///
185184
/// Only prunes if the number of IP paths is above [`MAX_IP_PATHS`].
186185
///
187-
/// Keeps paths that are active or have never been holepunched.
186+
/// Keeps paths that are open or of unknown status.
188187
///
189188
/// Always prunes paths that have unsuccessfully holepunched.
190189
///
@@ -216,16 +215,18 @@ fn prune_ip_paths(paths: &mut FxHashMap<transports::Addr, PathState>) {
216215
let mut failed = Vec::with_capacity(ip_paths.len());
217216

218217
for (addr, state) in ip_paths {
219-
// ignore paths that have never been abandoned, they are either currently
220-
// open or were never dialed.
221-
if let Some(abandoned) = state.abandoned {
222-
if state.usable {
218+
match state.status {
219+
PathStatus::Inactive(t) => {
223220
// paths where holepunching succeeded at one point, but the path was closed.
224-
inactive.push((addr.clone(), abandoned));
225-
} else {
221+
inactive.push((addr.clone(), t));
222+
}
223+
PathStatus::Unusable => {
226224
// paths where holepunching has been attempted and failed.
227225
failed.push(addr.clone());
228226
}
227+
_ => {
228+
// ignore paths that are open or the status is unknown
229+
}
229230
}
230231
}
231232

@@ -268,19 +269,17 @@ mod tests {
268269
transports::Addr::Ip(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into())
269270
}
270271

271-
fn path_state_usable_abandoned(abandoned: Instant) -> PathState {
272+
fn path_state_inactive(closed: Instant) -> PathState {
272273
PathState {
273274
sources: HashMap::new(),
274-
usable: true,
275-
abandoned: Some(abandoned),
275+
status: PathStatus::Inactive(closed),
276276
}
277277
}
278278

279-
fn path_state_failed_abandoned(abandoned: Instant) -> PathState {
279+
fn path_state_unusable() -> PathState {
280280
PathState {
281281
sources: HashMap::new(),
282-
usable: false,
283-
abandoned: Some(abandoned),
282+
status: PathStatus::Unusable,
284283
}
285284
}
286285

@@ -310,7 +309,6 @@ mod tests {
310309
#[test]
311310
fn test_prune_failed_holepunch() {
312311
let mut paths = FxHashMap::default();
313-
let now = Instant::now();
314312

315313
// Add 20 active paths
316314
for i in 0..20 {
@@ -319,7 +317,7 @@ mod tests {
319317

320318
// Add 15 failed holepunch paths (must_prune)
321319
for i in 20..35 {
322-
paths.insert(ip_addr(i), path_state_failed_abandoned(now));
320+
paths.insert(ip_addr(i), path_state_unusable());
323321
}
324322

325323
prune_ip_paths(&mut paths);
@@ -344,14 +342,11 @@ mod tests {
344342
paths.insert(ip_addr(i), PathState::default());
345343
}
346344

347-
// Add 20 usable but abandoned paths with different abandon times
345+
// Add 20 inactive paths with different abandon times
348346
// Ports 15-34, with port 34 being most recently abandoned
349347
for i in 0..20 {
350348
let abandoned_time = now - Duration::from_secs((20 - i) as u64);
351-
paths.insert(
352-
ip_addr(15 + i as u16),
353-
path_state_usable_abandoned(abandoned_time),
354-
);
349+
paths.insert(ip_addr(15 + i as u16), path_state_inactive(abandoned_time));
355350
}
356351

357352
assert_eq!(35, paths.len());
@@ -392,16 +387,13 @@ mod tests {
392387

393388
// Add 5 failed holepunch paths
394389
for i in 15..20 {
395-
paths.insert(ip_addr(i), path_state_failed_abandoned(now));
390+
paths.insert(ip_addr(i), path_state_unusable());
396391
}
397392

398393
// Add 15 usable but abandoned paths
399394
for i in 0..15 {
400395
let abandoned_time = now - Duration::from_secs((15 - i) as u64);
401-
paths.insert(
402-
ip_addr(20 + i as u16),
403-
path_state_usable_abandoned(abandoned_time),
404-
);
396+
paths.insert(ip_addr(20 + i as u16), path_state_inactive(abandoned_time));
405397
}
406398

407399
assert_eq!(35, paths.len());
@@ -430,11 +422,10 @@ mod tests {
430422
#[test]
431423
fn test_prune_non_ip_paths_not_counted() {
432424
let mut paths = FxHashMap::default();
433-
let now = Instant::now();
434425

435426
// Add 25 IP paths (under MAX_IP_PATHS)
436427
for i in 0..25 {
437-
paths.insert(ip_addr(i), path_state_failed_abandoned(now));
428+
paths.insert(ip_addr(i), path_state_unusable());
438429
}
439430

440431
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
@@ -458,16 +449,15 @@ mod tests {
458449
#[test]
459450
fn test_prune_preserves_never_dialed() {
460451
let mut paths = FxHashMap::default();
461-
let now = Instant::now();
462452

463-
// Add 20 never-dialed paths (abandoned = None, usable = false)
453+
// Add 20 never-dialed paths (PathStatus::Unknown)
464454
for i in 0..20 {
465455
paths.insert(ip_addr(i), PathState::default());
466456
}
467457

468458
// Add 15 failed paths to trigger pruning
469459
for i in 20..35 {
470-
paths.insert(ip_addr(i), path_state_failed_abandoned(now));
460+
paths.insert(ip_addr(i), path_state_unusable());
471461
}
472462

473463
prune_ip_paths(&mut paths);
@@ -481,11 +471,10 @@ mod tests {
481471
#[test]
482472
fn test_prune_all_paths_failed() {
483473
let mut paths = FxHashMap::default();
484-
let now = Instant::now();
485474

486475
// Add 40 failed holepunch paths (all paths have failed)
487476
for i in 0..40 {
488-
paths.insert(ip_addr(i), path_state_failed_abandoned(now));
477+
paths.insert(ip_addr(i), path_state_unusable());
489478
}
490479

491480
assert_eq!(40, paths.len());
@@ -499,4 +488,66 @@ mod tests {
499488
"should keep MAX_IP_PATHS when all paths failed"
500489
);
501490
}
491+
492+
#[test]
493+
fn test_insert_open_path() {
494+
let mut state = RemotePathState::default();
495+
let addr = ip_addr(1000);
496+
let source = Source::Udp;
497+
498+
assert!(state.is_empty());
499+
500+
state.insert_open_path(addr.clone(), source.clone());
501+
502+
assert!(!state.is_empty());
503+
assert!(state.paths.contains_key(&addr));
504+
let path = &state.paths[&addr];
505+
assert!(matches!(path.status, PathStatus::Open));
506+
assert_eq!(path.sources.len(), 1);
507+
assert!(path.sources.contains_key(&source));
508+
}
509+
510+
#[test]
511+
fn test_abandoned_path() {
512+
let mut state = RemotePathState::default();
513+
514+
// Test: Open goes to Inactive
515+
let addr_open = ip_addr(1000);
516+
state.insert_open_path(addr_open.clone(), Source::Udp);
517+
assert!(matches!(state.paths[&addr_open].status, PathStatus::Open));
518+
519+
state.abandoned_path(&addr_open);
520+
assert!(matches!(
521+
state.paths[&addr_open].status,
522+
PathStatus::Inactive(_)
523+
));
524+
525+
// Test: Inactive stays Inactive
526+
state.abandoned_path(&addr_open);
527+
assert!(matches!(
528+
state.paths[&addr_open].status,
529+
PathStatus::Inactive(_)
530+
));
531+
532+
// Test: Unknown goes to Unusable
533+
let addr_unknown = ip_addr(2000);
534+
state.insert_multiple([addr_unknown.clone()].into_iter(), Source::Relay);
535+
assert!(matches!(
536+
state.paths[&addr_unknown].status,
537+
PathStatus::Unknown
538+
));
539+
540+
state.abandoned_path(&addr_unknown);
541+
assert!(matches!(
542+
state.paths[&addr_unknown].status,
543+
PathStatus::Unusable
544+
));
545+
546+
// Test: Unusable stays Unusable
547+
state.abandoned_path(&addr_unknown);
548+
assert!(matches!(
549+
state.paths[&addr_unknown].status,
550+
PathStatus::Unusable
551+
));
552+
}
502553
}

0 commit comments

Comments
 (0)