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
WinRT Futures should update their inner Waker #342
Comments
Thanks for the repro, Nathaniel! That's super helpful. A wrapper may be the way to go, but I really don't enjoy all the verbiage Rust requires with all the unwrapping so I'll think about generating the projection to possibly return the wrapper directly so that it doesn't require another method call. |
I was thinking that some of the verbiage could be avoided by making the conversion method not return a result, like this: // Work-around being unable to implement new functions on foreign types
trait IAsyncActionExt {
fn to_future(self) -> IAsyncActionFuture;
}
impl IAsyncActionExt for IAsyncAction {
fn to_future(self) -> IAsyncActionFuture {
IAsyncActionFuture {
inner: self,
shared_waker: Arc::new(Mutex::new(None)),
}
}
}
struct IAsyncActionFuture {
inner: IAsyncAction,
shared_waker: Arc<Mutex<Option<Waker>>>,
}
impl Future for IAsyncActionFuture {
type Output = winrt::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.inner.status()? == AsyncStatus::Started {
let mut shared_waker = self.shared_waker.lock().unwrap();
let shared_waker_is_none = shared_waker.is_none();
*shared_waker = Some(cx.waker().clone());
if shared_waker_is_none {
let shared_waker = self.shared_waker.clone();
self.inner.set_completed(AsyncActionCompletedHandler::new(
move |_sender, _args| {
// The waker will always be some here
shared_waker.lock().unwrap().take().unwrap().wake();
Ok(())
},
))?;
}
Poll::Pending
} else {
Poll::Ready(self.inner.get_results())
}
}
}
fn main() {
let mut tokio_rt = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap();
tokio_rt.block_on(async {
let fut_1 = TestRunner::create_async_action(1000).unwrap().to_future();
let fut_2 = TestRunner::create_async_action(2000).unwrap().to_future();
let mut fut_1_fuse = fut_1.fuse();
let mut fut_2_fuse = fut_2.fuse();
let incomplete_future = futures::select! {
res_1 = fut_1_fuse => fut_2_fuse,
res_2 = fut_2_fuse => fut_1_fuse,
};
println!("1 future complete, finishing other on background task...");
// Work-around !Send future, this example still works with Send winrt futures and tokio::spawn.
let local = tokio::task::LocalSet::new();
local.spawn_local(async move {
incomplete_future.await.unwrap();
});
local.await;
println!("Both futures complete!");
});
println!("Done!");
} The only job of the conversion method is to essentially create a wrapper around an |
|
Thanks for the reminder. Yes, now that |
Opening this issue as a continuation of #322. While it is now possible to call
poll
on winrt futures more than once, consecutivepoll
calls will not update theWaker
. As a result, these futures will hang forever if a future is transferred between tasks after being polled. A simple example of this behavior:This example will hang indefinitely and never complete. Unfortunately, I don't this its possible to fix this while maintaining the
Future
impl onIAsyncxxx
as some extra memory is needed to store the shared reference to theWaker
to update if needed.In my opinion, IntoFuture is probably the best way forward, however it is currently unstable. Implementing this trait will allow
await
-ing on that value by implicity callinginto_future
, just as theIntoIterator
trait allows iterating over a value that doesn't explicitly implementIterator
. In the meantime, maybe we could remove theFuture
impl fromIAsyncxxx
, replacing it with a method to get a wrapper struct implementingFuture
?The text was updated successfully, but these errors were encountered: