Skip to content

Conversation

@matthiasbeyer
Copy link
Contributor

@matthiasbeyer matthiasbeyer commented Oct 25, 2025

Description

Following #3575 (which was inspired by me complaining on your discord that this was missing from the interface) and the discussion on your discord, I figured I would fancy a slightly different approach to that PR and the interfaces introduced in your PR there.

This is twofold:

  • Replace the RelayMap::extend with an actual impl Extend ror RelayMap (that can easily be used with an existing RelayMap by calling rm.relays::<Vec<_>>() on it
  • Rewrite the existing RelayMap::extend with an RelayMap::join. Reason here is that my usecase (that, again, inspired this to be added) is to build a RelayMap object with defaults + custom entries before using them, so there's no need to update (.extend()ing) an existing RelayMap during runtime. This is still preserved via the impl Extend though.

Tell me what you think! CCing people from discord:

Breaking Changes

  • RelayMap::extend (unreleased) -> RelayMap::join

Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing the example in the extend docs! It's really nice to have a trait impl documented, should be done more often! Also really helpful here because of the awkward ::Vec<_> bit.

/// Extends this `RelayMap` with another one.
pub fn extend(&self, other: &RelayMap) {
/// Joins this `RelayMap` with another one into a new one
pub fn join(self, other: RelayMap) -> RelayMap {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this called chain usually? I think this is very similar to the Iterator::chain signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is. I opted for join because a Map is not something that can (semantically) be put into a ⛓️ chain! 😉 I think of chain() something that can be applied to a sequence of something (hence Iterator::chain but not BTreeMap::chain for example).

I have no strong feelings about the actual method name tho, so if you want me to change it, I'll happily do so.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced passing owned versions makes sense for a fully clonable thing. This API really doesn't communicate the fact that this doesn't actually create a new version, but rather modifies the underlying, likely shared instances

pub fn extend(&self, other: &RelayMap) {
/// Joins this `RelayMap` with another one into a new one
pub fn join(self, other: RelayMap) -> RelayMap {
// SAFETY: We have exclusive access to this object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate documenting this, though I think this is slightly incorrect:

  • SAFETY is used to document unsafe code, panicking is not unsafe (tokio is full of panicking functions!)
  • This is not about exclusive access, the assertion here is that the lock is not poisoned. That is, there was no panic while holding the lock. The reasoning is that if there was a panic while holding the lock the internal invariants that the lock protects may not be upheld.

But writing that reasoning out, you can probably avoid panicking here I think. Because there are very simple internal variants you can probably just extract the internal state from the poisoned lock and put it back in there. It does require you to audit all the write paths to make sure they don't contain panicking code themselves though, because otherwise you silently lose some state. But all the write paths are internal via the functions so that's possible. It's a bunch of work for marginal gain 😄 but you can get the ideal of no panics. (Which has some small benefits to generated code size IIRC, but we've never really started optimising for this yet).

Anyway, the panics on poison are perfectly fine as well :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SAFETY is used to document unsafe code, panicking is not unsafe (tokio is full of panicking functions!)

True. Maybe // This is safe because... rather?

This is not about exclusive access, the assertion here is that the lock is not poisoned. [...]

Also true. I need to change that.

About your last bit: Yes, but that's probably not worth the effort.

@matthiasbeyer
Copy link
Contributor Author

Added some fixups for the misleading comment.

@flub flub changed the title Relay map extend ref(iroh): Relay map extend Oct 25, 2025
@ramfox
Copy link
Member

ramfox commented Oct 30, 2025

@matthiasbeyer we are doing a release on monday! Would love to get this in if you could fix the failing CI.

@n0bot n0bot bot added this to iroh Oct 30, 2025
@github-project-automation github-project-automation bot moved this to 🏗 In progress in iroh Oct 30, 2025
@matthiasbeyer
Copy link
Contributor Author

This should suffice.

I can fix issues until tomorrow, Saturday, around 13:00 UTC. After that I am enjoying Hans Zimmer and on Sunday I will be traveling 😉 👍

/// # let relay_map_a: RelayMap = { unimplemented!() };
/// # let relay_map_b: RelayMap = { unimplemented!() };
///
/// relay_map_a.extend(relay_map_b.relays::<Vec<_>>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is less efficient, and seems rather pointless to support, if you have to collect & allocate into a vector first before using the trait

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner lock seems to stop you from doing anything nice using IntoIterator or Extend. 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's the issue yes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this would be a DashMap, I guess it would work.

That also would mean that not the whole map must be locked when adding/removing.

But it introduces other things that are dangerous, such as holding dashmap locks over await points (which can be linted against though).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you lint against that? I didn't know that was possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But clippy can do that (by now) via the await-holding-invalid-types lint: https://rust-lang.github.io/rust-clippy/stable/index.html#await_holding_invalid_type

Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main objection to the current API is that extend overlaps with Extend::extend? But implementing the trait is really ugly due to the internal locks.

The existing API does guide you to do the right thing, which really is what matters. I'm not sure supporting the Extend trait directly does improve ergonomics. So overall I think I'm -1 on this so far.

/// # let relay_map_a: RelayMap = { unimplemented!() };
/// # let relay_map_b: RelayMap = { unimplemented!() };
///
/// relay_map_a.extend(relay_map_b.relays::<Vec<_>>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner lock seems to stop you from doing anything nice using IntoIterator or Extend. 😞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

4 participants