Change Future into Async Function
(Jin Qing’s Column, Dec., 2024)
Source code: connection.rs
Change struct StreamUpgrade
into async function to simplify the code.
StreamUpgrade
is used as a Future, which can be replaced by an async function.
impl<UserData, TOk, TErr> Future for StreamUpgrade<UserData, TOk, TErr> {
type Output = (UserData, Result<TOk, StreamUpgradeError<TErr>>);
...
}
Change the constructor StreamUpgrade::new_inbound()
into async function
Original constructor and poll()
impl<UserData, TOk, TErr> StreamUpgrade<UserData, TOk, TErr> {
fn new_inbound<Upgrade>(
substream: SubstreamBox,
protocol: SubstreamProtocol<Upgrade, UserData>,
counter: ActiveStreamCounter,
) -> Self
where
Upgrade: InboundUpgradeSend<Output = TOk, Error = TErr>,
{
let timeout = *protocol.timeout();
let (upgrade, open_info) = protocol.into_upgrade();
let protocols = upgrade.protocol_info();
Self {
user_data: Some(open_info),
timeout: Delay::new(timeout),
upgrade: Box::pin(async move {
let (info, stream) =
multistream_select::listener_select_proto(substream, protocols)
.await
.map_err(to_stream_upgrade_error)?;
let output = upgrade
.upgrade_inbound(Stream::new(stream, counter), info)
.await
.map_err(StreamUpgradeError::Apply)?;
Ok(output)
}),
}
}
}
impl<UserData, TOk, TErr> Future for StreamUpgrade<UserData, TOk, TErr> {
type Output = (UserData, Result<TOk, StreamUpgradeError<TErr>>);
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match self.timeout.poll_unpin(cx) {
Poll::Ready(()) => {
return Poll::Ready((
self.user_data
.take()
.expect("Future not to be polled again once ready."),
Err(StreamUpgradeError::Timeout),
))
}
Poll::Pending => {}
}
let result = futures::ready!(self.upgrade.poll_unpin(cx));
let user_data = self
.user_data
.take()
.expect("Future not to be polled again once ready.");
Poll::Ready((user_data, result))
}
}
New async function:
async fn new_inbound_upgrade_stream<UserData, TOk, TErr, Upgrade>(
substream: SubstreamBox,
protocol: SubstreamProtocol<Upgrade, UserData>,
counter: ActiveStreamCounter,
) -> (UserData, Result<TOk, StreamUpgradeError<TErr>>)
where
Upgrade: InboundUpgradeSend<Output = TOk, Error = TErr>,
{
let timeout = *protocol.timeout();
let (upgrade, open_info) = protocol.into_upgrade();
let fut = new_inbound_upgrade_stream_inner(substream, upgrade, counter);
let res = tokio::time::timeout(timeout, fut).await;
if let Ok(res) = res {
return (open_info, res);
}
(open_info, Err(StreamUpgradeError::Timeout))
}
async fn new_inbound_upgrade_stream_inner<TOk, TErr, Upgrade>(
substream: SubstreamBox,
upgrade: Upgrade,
counter: ActiveStreamCounter,
) -> Result<TOk, StreamUpgradeError<TErr>>
where
Upgrade: InboundUpgradeSend<Output = TOk, Error = TErr>,
{
let protocols = upgrade.protocol_info();
let (info, stream) = multistream_select::listener_select_proto(substream, protocols)
.await
.map_err(to_stream_upgrade_error)?;
let output = upgrade
.upgrade_inbound(Stream::new(stream, counter), info)
.await
.map_err(StreamUpgradeError::Apply)?;
Ok(output)
}
Change the usage of StreamUpgrade
Change
/// Futures that upgrade incoming substreams.
negotiating_in: FuturesUnordered<
StreamUpgrade<
THandler::InboundOpenInfo,
<THandler::InboundProtocol as InboundUpgradeSend>::Output,
<THandler::InboundProtocol as InboundUpgradeSend>::Error,
>,
>,
into
negotiating_in: FuturesUnordered<Pin<Box<dyn
Future<Output = (
THandler::InboundOpenInfo,
Result<
<THandler::InboundProtocol as InboundUpgradeSend>::Output,
StreamUpgradeError<<THandler::InboundProtocol as InboundUpgradeSend>::Error>,
>,
)> + Send,
>>>,
And change
negotiating_in.push(StreamUpgrade::new_inbound(
substream,
protocol,
stream_counter.clone(),
));
into
negotiating_in.push(new_inbound_upgrade_stream(
substream,
protocol,
stream_counter.clone(),
).boxed());