Skip to content

Waking the current task leads to a deadlock #3

@piodul

Description

@piodul

If a Rust future is co_awaited from C++ and that future decides to wake the current task, then a deadlock will occur.

Found while trying to use futures::FuturesUnordered which wakes the current task here.

I attach a minimal example below.

main.hh:

#pragma once

#include "rust/cxx.h"
#include "cxx-async/include/rust/cxx_async.h"

CXXASYNC_DEFINE_FUTURE(void, RustFutureVoid);

RustFutureVoid run_cpp();

main.cc:

#include "main.hh"
#include "cxx-async-self-wake-bug/src/main.rs.h"

RustFutureVoid run_cpp() {
    co_await run_rust();
}

main.rs:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

use crate::ffi::run_cpp;

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        fn run_rust() -> RustFutureVoid;
    }
    unsafe extern "C++" {
        include!("main.hh");
        fn run_cpp() -> RustFutureVoid;
        type RustFutureVoid = crate::RustFutureVoid;
    }
}

#[cxx_async::bridge]
unsafe impl Future for RustFutureVoid {
    type Output = ();
}

pub fn run_rust() -> RustFutureVoid {
    RustFutureVoid::infallible(YieldFuture { yielded: false })
}

struct YieldFuture {
    yielded: bool,
}

impl Future for YieldFuture {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if !self.yielded {
            // Yield, but also wake immediately
            self.as_mut().yielded = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        } else {
            Poll::Ready(())
        }
    }
}

fn main() {
    println!("Start!");
    futures::executor::block_on(run_cpp()).unwrap();
    println!("Finish!");
}

Only "Start!" is printed, then the program deadlocks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions