ohadravid / wmi-rs

WMI crate for rust
Apache License 2.0
80 stars 27 forks source link

Cannot async within traits #85

Open NuSkooler opened 10 months ago

NuSkooler commented 10 months ago

I'm able to use async_query() in simple tests, but not within an async trait. A simple example:

#[async_trait]
trait TestWmiTrait {
    async fn do_query() -> Result<()>;
}

struct TestWmi;
#[async_trait]
impl TestWmiTrait for TestWmi {
    async fn do_query() -> Result<()> {
        let com_con = COMLibrary::new()?;
        let wmi_con = WMIConnection::new(com_con.into())?;
        let _: Vec<UserProfile> = wmi_con.async_query().await?;
        Ok(())
    }
}

#[tokio::test]
async fn can_query_wmi_async() -> Result<()> {
    TestWmi::do_query().await?;
    Ok(())
}

Results in the following:

error: future cannot be sent between threads safely
   --> crates\bds_host\src\account\windows.rs:412:43
    |
412 |           async fn do_query() -> Result<()> {
    |  ___________________________________________^
413 | |             let com_con = COMLibrary::new()?;
414 | |             let wmi_con = WMIConnection::new(com_con.into())?;
415 | |             let _: Vec<UserProfile> = wmi_con.async_query().await?;
416 | |             Ok(())
417 | |         }
    | |_________^ future created by async block is not `Send`
    |
    = help: within `[async block@crates\bds_host\src\account\windows.rs:412:43: 417:10]`, the trait `Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
   --> crates\bds_host\src\account\windows.rs:415:61
    |
413 |             let com_con = COMLibrary::new()?;
    |                 ------- has type `wmi::COMLibrary` which is not `Send`
414 |             let wmi_con = WMIConnection::new(com_con.into())?;
415 |             let _: Vec<UserProfile> = wmi_con.async_query().await?;
    |                                                             ^^^^^ await occurs here, with `com_con` maybe used later
416 |             Ok(())
417 |         }
    |         - `com_con` is later dropped here
    = note: required for the cast from `Pin<Box<[async block@crates\bds_host\src\account\windows.rs:412:43: 417:10]>>` to `Pin<Box<(dyn Future<Output = std::result::Result<(), anyhow::Error>> + Send + 'async_trait)>>

Any ideas? Note that the same code works outside of a trait method.

ohadravid commented 10 months ago

Hi @NuSkooler 👋

That's because WMI needs to be initialized per-thread, so any future that uses the connection cannot move between threads (aka, the resulting futures are !Send).

async_trait supports this, so using #[async_trait(?Send)] should fix this.

Let me know if this helps!

NuSkooler commented 10 months ago

@ohadravid Thanks, that makes sense. This does leave me in a dilemma, though it's likely my being fairly new to Rust: Removing Send trickles throughout the code meaning I can from my understanding, no longer use this method in other async/tokio code.

Is there a work around?

The work around I'm using right now is hoisting the work to non-async code that's wrapped with Tokio spawn_blocking, which doesn't feel that great.

ohadravid commented 10 months ago

Maybe something like https://github.com/ohadravid/wmi-rs/issues/55#issuecomment-1214223034 can help in your case?

NuSkooler commented 9 months ago

@ohadravid That looks great, I'll check it out. Thanks!