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

swarm/src/connection: Prioritize handler over negotiating streams #2638

Merged
merged 5 commits into from
May 18, 2022

Conversation

mxinden
Copy link
Member

@mxinden mxinden commented May 10, 2022

Description

The HandlerWrapper polls three components:

  1. ConnectionHandler
  2. Outbound negotiating streams
  3. Inbound negotiating streams

The ConnectionHandler itself might itself poll already negotiated streams.

By polling the three components above in the listed order one:

  • Prioritizes local work and work coming from negotiated streams over
    negotiating streams.
  • Prioritizes outbound negotiating streams over inbound negotiating
    streams, i.e. outbound requests over inbound requests.

Links to any relevant issues

Follows the general theme of #2627 and #2626.

Change checklist

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
    • No change in functionality.
  • I have added tests that prove my fix is effective or that my feature works
  • A changelog entry has been made in the appropriate crates
    • No change in functionality.

The `HandlerWrapper` polls three components:

1. `ConnectionHandler`
2. Outbound negotiating streams
3. Inbound negotiating streams

The `ConnectionHandler` itself might itself poll already negotiated streams.

By polling the three components above in the listed order one:

- Prioritizes local work and work coming from negotiated streams over
  negotiating streams.
- Prioritizes outbound negotiating streams over inbound negotiating
  streams, i.e. outbound requests over inbound requests.
}

// After the `inject_*` calls, the [`ConnectionHandler`] might be able to make progress.
continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could also recurse here and thus avoid the loop? Not sure if that is necessarily easier to understand.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't follow. What do you mean with "recurse"? As in a recursive call to the same method?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah. You should be able to just call self.poll here I believe?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have not seen recursion used on any serious Rust project. Not saying that it does not exist. Thus far I have avoided recursion in Rust due to:

  • No Tail Call Ellimination
  • Call stack limits
  • Sensing that this is not idiomatic, thus not intuitive

Am I missing something? Do the above arguments not apply here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think in this particular case, the recursion is very likely to stop after one call, is it not?

But I understand that this might be too much of a detail to notice and thus make the code harder to understand. Also, it is not guaranteed and thus risky to use with the usual problems of recursion.

Happy for the loop to stay, was just an idea :)

Comment on lines 313 to 327
match res {
ConnectionHandlerEvent::Custom(event) => {
return Poll::Ready(Ok(Event::Custom(event)));
}
ConnectionHandlerEvent::OutboundSubstreamRequest { protocol } => {
let id = self.unique_dial_upgrade_id;
let timeout = *protocol.timeout();
self.unique_dial_upgrade_id += 1;
let (upgrade, info) = protocol.into_upgrade();
self.queued_dial_upgrades.push((id, SendWrapper(upgrade)));
return Poll::Ready(Ok(Event::OutboundSubstreamRequest((
id, info, timeout,
))));
}
ConnectionHandlerEvent::Close(err) => return Poll::Ready(Err(err.into())),
Copy link
Contributor

Choose a reason for hiding this comment

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

It is not immediately clear that all these branches will return because of the noise of constructing the return value. We could extract this into a separate function (self.poll_handler(cx)) and only have 1 return statement to make this more obvious. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you think of 1de8039 @thomaseizinger?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that is better :)

Copy link
Contributor

@elenaf9 elenaf9 left a comment

Choose a reason for hiding this comment

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

LGTM.
One question (not related to this PR but something I was wondering about while reviewing):
When asking the handler whether to keep the connection alive or not, in case of KeepAlive::Until it checks if the "until" timestamp is in the future.
If it is, we set the Shutdown::Later accordingly.
But in case that it is in the past (checked_duration_since returns None), we do not do anything. Shouldn't in that case the shutdown be set to Shutdown::Asap?

(_, KeepAlive::Until(t)) => {
if let Some(dur) = t.checked_duration_since(Instant::now()) {
self.shutdown = Shutdown::Later(Delay::new(dur), t)
}
}

Am I missing something?

@mxinden
Copy link
Member Author

mxinden commented May 17, 2022

One question (not related to this PR but something I was wondering about while reviewing): When asking the handler whether to keep the connection alive or not, in case of KeepAlive::Until it checks if the "until" timestamp is in the future. If it is, we set the Shutdown::Later accordingly. But in case that it is in the past (checked_duration_since returns None), we do not do anything. Shouldn't in that case the shutdown be set to Shutdown::Asap?

(_, KeepAlive::Until(t)) => {
if let Some(dur) = t.checked_duration_since(Instant::now()) {
self.shutdown = Shutdown::Later(Delay::new(dur), t)
}
}

Not sure what the reasoning for this was. I could imagine that a KeepAlive::Until with a time in the past was considered a mistake. That said, I agree that Shutdown::Asap makes more sense. Want to create a patch @elenaf9?

@mxinden mxinden merged commit 9a5fec8 into libp2p:master May 18, 2022
@mxinden mxinden mentioned this pull request May 29, 2022
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants