Skip to content

Commit

Permalink
refactor(iroh): magical pin projections (#3060)
Browse files Browse the repository at this point in the history
## Description

Avoids the `Box::pin` inside of the loop

## Breaking Changes

<!-- Optional, if there are any breaking changes document them,
including how to migrate older code. -->

## Notes & open questions

<!-- Any notes, remarks or open questions you have to make about the PR.
-->

## Change checklist

- [x] Self-review.
- [x] Documentation updates following the [style
guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text),
if relevant.
- [x] Tests if relevant.
- [x] All breaking changes documented.

---------

Co-authored-by: Floris Bruynooghe <[email protected]>
  • Loading branch information
dignifiedquire and flub authored Dec 18, 2024
1 parent 272b6c4 commit a4d4e7d
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 24 deletions.
6 changes: 3 additions & 3 deletions iroh/src/magicsock/relay_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ impl ActiveRelayActor {
// When this future has an inner, it is a future which is currently sending
// something to the relay server. Nothing else can be sent to the relay server at
// the same time.
let mut relay_send_fut = MaybeFuture::none();
let mut relay_send_fut = std::pin::pin!(MaybeFuture::none());

loop {
// If a read error occurred on the connection it might have been lost. But we
Expand All @@ -204,15 +204,15 @@ impl ActiveRelayActor {
}
// Only poll relay_send_fut if it is sending to the relay.
_ = &mut relay_send_fut, if relay_send_fut.is_some() => {
relay_send_fut = MaybeFuture::none();
relay_send_fut.as_mut().set_none();
}
// Only poll for new datagrams if relay_send_fut is not busy.
Some(msg) = self.relay_datagrams_send.recv(), if relay_send_fut.is_none() => {
let relay_client = self.relay_client.clone();
let fut = async move {
relay_client.send(msg.node_id, msg.packet).await
};
relay_send_fut = MaybeFuture::with_future(Box::pin(fut));
relay_send_fut.as_mut().set_future(fut);
self.last_write = Instant::now();

}
Expand Down
46 changes: 25 additions & 21 deletions iroh/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,58 @@ use std::{
task::{Context, Poll},
};

use pin_project::pin_project;

/// A future which may not be present.
///
/// This is a single type which may optionally contain a future. If there is no inner
/// future polling will always return [`Poll::Pending`].
///
/// The [`Default`] impl will create a [`MaybeFuture`] without an inner.
#[derive(Debug)]
pub(crate) struct MaybeFuture<T> {
#[derive(Default, Debug)]
#[pin_project(project = MaybeFutureProj, project_replace = MaybeFutureProjReplace)]
pub(crate) enum MaybeFuture<T> {
/// Future to be polled.
pub inner: Option<T>,
Some(#[pin] T),
#[default]
None,
}

impl<T> MaybeFuture<T> {
/// Creates a [`MaybeFuture`] without an inner future.
pub(crate) fn none() -> Self {
Self { inner: None }
Self::default()
}

/// Clears the value
pub(crate) fn set_none(mut self: Pin<&mut Self>) {
self.as_mut().project_replace(Self::None);
}

/// Creates a [`MaybeFuture`] with an inner future.
pub(crate) fn with_future(fut: T) -> Self {
Self { inner: Some(fut) }
/// Sets a new future.
pub(crate) fn set_future(mut self: Pin<&mut Self>, fut: T) {
self.as_mut().project_replace(Self::Some(fut));
}

/// Returns `true` if the inner is empty.
pub(crate) fn is_none(&self) -> bool {
self.inner.is_none()
matches!(self, Self::None)
}

/// Returns `true` if the inner contains a future.
pub(crate) fn is_some(&self) -> bool {
self.inner.is_some()
}
}

// NOTE: explicit implementation to bypass derive unnecessary bounds
impl<T> Default for MaybeFuture<T> {
fn default() -> Self {
Self::none()
matches!(self, Self::Some(_))
}
}

impl<T: Future + Unpin> Future for MaybeFuture<T> {
impl<T: Future> Future for MaybeFuture<T> {
type Output = T::Output;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.inner {
Some(ref mut t) => Pin::new(t).poll(cx),
None => Poll::Pending,
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
match this {
MaybeFutureProj::Some(t) => t.poll(cx),
MaybeFutureProj::None => Poll::Pending,
}
}
}
Expand Down

0 comments on commit a4d4e7d

Please sign in to comment.