Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(iroh): Add send queue between relay actor and relays #3026

Merged
merged 5 commits into from
Dec 12, 2024

Conversation

flub
Copy link
Contributor

@flub flub commented Dec 10, 2024

Description

This introduces a new packet send queue between the RelayActor and the various ConnectedRelayActors. Each ConnectedRelayActor will not block while sending them to the actual relay client.

This is important for the relay client to become nothing more than a stream and sink directly connected to the network stream in future refactoring.

It does not yet change the communication between
AsyncUdpSocket::try_send and the RelayActor itself to keep this change smaller. This also should become a separate channel so that the inbox can still handle messages when the relay channel is full.

This is a small incremental change that does a few not very nice things, like having some .expect() calls. This is deliberate to keep the changes smaller to review and reason about.

Breaking Changes

Notes & open questions

Pay most attention to the ConnectedRelayActor and the way it does create a send future without blocking. The exact mechanism will change once it is sending to a stream, but in the mean time this needs to be somewhat reasonable.

There are still tons of bugs in here. But I don't think there are any regressions. All the bugs existed already.

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All breaking changes documented.

This introduces a new packet send queue between the RelayActor and the
various ConnectedRelayActors.  Each ConnectedRelayActor will not block
while sending them to the actual relay client.

This is important for the relay client to become nothing more than a
stream and sink directly connected to the network stream in future
refactoring.

It does not yet change the communication between
AsyncUdpSocket::try_send and the RelayActor itself to keep this change
smaller.  This also should become a separate channel so that the inbox
can still handle messages when the relay channel is full.

This is a small incremental change that does a few not very nice
things, like having some .expect() calls.  This is deliberate to keep
the changes smaller to review and reason about.
Copy link

github-actions bot commented Dec 10, 2024

Documentation for this PR has been generated and is available at: https://n0-computer.github.io/iroh/pr/3026/docs/iroh/

Last updated: 2024-12-12T12:19:24Z

@flub flub marked this pull request as ready for review December 10, 2024 14:43
@flub flub requested a review from a team December 10, 2024 14:44
@flub flub added this to the v0.30.0 milestone Dec 10, 2024
Copy link

github-actions bot commented Dec 10, 2024

Netsim report & logs for this PR have been generated and is available at: LOGS
This report will remain available for 3 days.

Last updated for commit: 0ab8c6e

relay_send_fut = Either::Left(future::pending());
}
Some(msg) = self.relay_datagrams_send.recv(), if matches!(relay_send_fut, Either::Left(_)) => {
relay_send_fut = Either::Right(Box::pin(relay_client.send(msg.0, msg.1)));
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just spawn these? or sth..this construction is very hard to follow..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really do not want to spawn a task here. The plan is for relay_client to be directly a AsyncWriter, probably wrapped in a Sink (wrapped in some different client of Client struct). So this actor's loop has to learn how to send things to the relay server while not deadlocking itself.

My approach here is to start doing this for just sending packets. The others which send to the relay are Ping and NotePreferred (and kind of GetLocalAddr because it queues up on the same actor inbox right now, but doesn't go all the way to the relay).

If you look at the points at which Ping and GetLocalAddr are being used: this is only when there network has changed and the RelayActor wants to be sure this connection still works. So at that time there's already a potential hiccup happening. I totally want to fix those too, but plan to do that by moving those things into the ConnectedRelayActor instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

could you use MaybeFuture to make this a bit simpler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some changes to MaybeFuture, agreed that this is nicer. PTAL

Copy link
Contributor

Choose a reason for hiding this comment

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

much easier to understand (at least for me) now, thanks

@@ -173,6 +157,41 @@ impl ConnectedRelayActor {
Ok(())
}

async fn handle_actor_msg(&mut self, msg: ConnectedRelayMessage) -> bool {
trace!("tick: inbox: {:?}", msg);
match msg {
Copy link
Contributor

Choose a reason for hiding this comment

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

are you sure all these should be preferred over regular writes? it seems some of these will trigger additional sending, eg Ping and NotePreferred, or amy I missing sth?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are not being preferred, the tokio::select! is not biased. The ones that do trigger a send are indeed blocking. I don't like it, but it is not a regression so in the interest of taking small steps I was only improving the SendPacket path.

#[derive(Debug)]
pub(crate) struct MaybeFuture<T> {
/// Future to be polled.
pub inner: Option<T>,
}

impl<T> MaybeFuture<T> {
/// Creates a [`MaybeFuture`] without an inner future.
pub(crate) fn none() -> Self {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've leant into the Option sematics and gone with ::none() constructor and .is_some() and .is_none(). This seems to match the naming which is almost OptionalFuture.

The alternative is to lean into collection APIs: ::empty() as constructor and .is_empty() to check (I guess .len() would be it's counterpart but the clippy convention is to use !m.is_empty()).

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the Option version

@flub flub enabled auto-merge December 12, 2024 12:17
@flub flub added this pull request to the merge queue Dec 12, 2024
Merged via the queue into main with commit af5a8c2 Dec 12, 2024
26 checks passed
@flub flub deleted the flub/relay-send-channel-1 branch December 12, 2024 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

2 participants